{
  "openapi": "3.1.0",
  "info": {
    "title": "InfraNode API",
    "version": "1.0.0",
    "description": "Open-Source-Proxy-REST-API für normalisierte offene Daten deutscher Großstädte. Bekannt sind 84 Städte über 100.000 Einwohner, davon 28 Kern-Städte mit voller Quellen-Abdeckung; weitere Städte werden über AGS- und geobasierte Quellen bedient. Jede Antwort folgt demselben kanonischen Envelope (data/meta/attribution) mit Lizenz-Tag und ehrlichem source_status (ok/no_data/disabled/not_covered). Teilabgedeckte Endpunkte (flood, webcams, traffic, road-events) liefern für eine nicht-abgedeckte Stadt 200 source_status=\"not_covered\" + meta.covered_cities statt eines leeren \"ok\"; die Abdeckung listet die Seite /abdeckung. Pfade tragen den vollen /api/v1-Prefix, damit die ausgelieferte Spec driftfrei zu den live registrierten Routen bleibt. Paginierung ist kanal-abhängig: direktes REST liefert Datenart-Listen voll, GPT-Actions und MCP sind serverseitig bzw. client-seitig auf ein Default-Limit gebunden; limit=all (bzw. ?all=1) erzwingt kanalunabhängig die Vollausgabe. Details siehe meta.pagination (Meta-Schema). Der Stadt-Slug im Pfad wird tolerant aufgelöst: der deutsche Name mit oder ohne Umlaute, beliebige Groß/Kleinschreibung, gängige englische Exonyme und Kurzformen führen zum kanonischen Slug (München/münchen/munich/munchen -> muenchen, cologne -> koeln, frankfurt -> frankfurt-am-main). Ein unbekannter Name liefert 404 mit einem \"Meintest du ...?\"-Hinweis auf den nächstliegenden Slug; die kanonischen Slugs listet GET /api/v1/cities. Feldnamen-Konvention (seit 2026-07-25 vereinheitlicht): Felder heissen durchgaengig snake_case und englisch. Gleiche Konzepte tragen ueberall denselben Namen: post_code (immer fuenfstelliger String, fuehrende Null bleibt erhalten), street, house_number, place, name, start, end, distance_km, power_kw, lat/lon. Fehlt eine Angabe in der Quelle, ist das Feld null statt Leerstring. Zeitstempel tragen immer eine Zeitzone. Uebergangsweise stehen die frueheren Namen (plz, zip, strasse, hausnummer, ort, city, bezeichnung, beginn, ende, art, dist_km, leistung_kw, einheit_typ sowie die camelCase-Rohfelder des Autobahn-Feeds) mit identischem Wert daneben; sie sind ABGEKUENDIGT und werden fruehestens 30 Tage nach der Ankuendigung im Changelog entfernt.",
    "license": {
      "name": "Apache-2.0",
      "url": "https://www.apache.org/licenses/LICENSE-2.0"
    }
  },
  "servers": [
    {
      "url": "https://infranode.dev",
      "description": "Öffentliche gehostete API, keylos (Standard)"
    },
    {
      "url": "http://localhost",
      "description": "Lokaler Caddy-Ingress (Docker Compose)"
    }
  ],
  "tags": [
    {
      "name": "meta",
      "description": "Meta- und Diagnose-Routen (Health, Ping, Source-Status, Spec)"
    },
    {
      "name": "cities",
      "description": "Stadt-Register und normalisierte Stadt-Stammdaten"
    },
    {
      "name": "compare",
      "description": "Multi-City-Vergleich einer Ressource über mehrere Städte"
    },
    {
      "name": "Live",
      "description": "Getrennte Live-Kategorie unter /api/v1/live. Minutenfrische bzw. quasi-live Quellen mit eigenem Envelope-Kennzeichen: meta.as_of (Datenstand) und meta.refresh_seconds (Kadenz). Re-exponiert zusätzlich bestehende Live-Endpunkte (air, air-uba, water-level, traffic, webcams, flood); deren Altpfade unter /api/v1/cities bleiben als Deprecation-Alias erhalten (kein Breaking Change)."
    },
    {
      "name": "stations",
      "description": "Per-Bahnhof-Live-Boards unter /api/v1/stations/{eva}. Abfahrten/Ankünfte JEDES DB-Bahnhofs über seine EVA-Nummer (aus dem Katalog /cities/{slug}/stations), alle Gattungen inkl. Nahverkehr, mit Echtzeit- Verspätung und Störungen/Meldungen. Quelle: DB Timetables (CC BY 4.0)."
    }
  ],
  "paths": {
    "/api/v1/health": {
      "get": {
        "tags": [
          "meta"
        ],
        "summary": "Liveness und Readiness der App prüfen",
        "x-summary-en": "Check app liveness and readiness",
        "description": "Liefert den App-Status und die Redis-Erreichbarkeit. Gedacht für Uptime-Monitore und Load-Balancer-Probes: antwortet mit 200, sobald die App bereit ist, sonst mit einem Fehlerstatus.",
        "x-description-en": "Returns the app status and Redis reachability. Designed for uptime monitors and load-balancer probes: responds with 200 once the app is ready, otherwise with an error status.",
        "operationId": "getHealth",
        "responses": {
          "200": {
            "description": "App läuft",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sources": {
      "get": {
        "tags": [
          "meta"
        ],
        "summary": "Status aller Upstream-Quellen",
        "x-summary-en": "Status of all upstream sources",
        "description": "Listet je bekannter Quelle den enabled-Zustand (aus den Settings) und den Circuit-Breaker-Zustand (CLOSED/OPEN/HALF_OPEN). So ist auf einen Blick sichtbar, welche Quelle gerade liefert und welche pausiert ist.",
        "x-description-en": "Lists each known source with its enabled state (from settings) and its circuit-breaker state (CLOSED/OPEN/HALF_OPEN). Shows at a glance which source is currently serving and which is paused.",
        "operationId": "getSources",
        "responses": {
          "200": {
            "description": "Liste der Quellen-Status",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/CacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SourceStatus"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          }
        },
        "parameters": [
          {
            "in": "header",
            "name": "If-None-Match",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Conditional GET. Stimmt der Wert mit dem aktuellen ETag überein, antwortet der Server mit 304 Not Modified ohne Body.",
            "x-description-en": "Conditional GET. If the value matches the current ETag, the server responds with 304 Not Modified and no body."
          },
          {
            "in": "query",
            "name": "page",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 50
            },
            "description": "Seitengröße. Wird auf MAX_LIMIT (200) gedeckelt: zu große Werte liefern eine 200er-Seite mit 200 Einträgen statt eines Fehlers.",
            "x-description-en": "Page size. Capped at MAX_LIMIT (200): larger values return a 200 page of 200 items instead of an error."
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Ein zu großer Offset liefert eine leere Seite (200), nie 500.",
            "x-description-en": "An offset past the end returns an empty page (200), never 500."
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string",
              "enum": [
                "source",
                "enabled",
                "license"
              ]
            },
            "description": "Nur die gelisteten Felder sind erlaubt; ein unbekanntes Feld wird mit 400 (invalid_request) abgewiesen, bevor es ausgewertet wird.",
            "x-description-en": "Only the listed fields are allowed; an unknown field is rejected with 400 (invalid_request) before it is evaluated."
          },
          {
            "in": "query",
            "name": "order",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          }
        ]
      }
    },
    "/api/v1/compare": {
      "get": {
        "tags": [
          "compare"
        ],
        "summary": "Eine Ressource über mehrere Städte vergleichen",
        "x-summary-en": "Compare one resource across multiple cities",
        "description": "Vergleicht EINE Ressource (resource) über mehrere Städte (cities, kommasepariert) in einer einzigen Antwort. Jede Stadt trägt einen eigenen source_status (ok/disabled/no_data/error/not_found); eine fehlende oder tote Stadt-Quelle führt nicht zu einem Gesamt-5xx, sondern wird pro Stadt ehrlich ausgewiesen. resource wird gegen eine feste Liste geprüft (unbekannt -> 400). Neben weather/air sind auch indicators, demographics, unemployment, tourism, charging-status und weather-warnings vergleichbar. Das Ergebnis ist paginierbar (page/limit/offset/sort).",
        "x-description-en": "Compares ONE resource across multiple cities (comma-separated) in a single response. Each city carries its own source_status (ok/disabled/no_data/error/not_found); a missing or dead city source does not cause an overall 5xx but is reported honestly per city. resource is checked against a fixed list (unknown -> 400). Besides weather/air, indicators, demographics, unemployment, tourism, charging-status and weather-warnings can be compared as well. The result is paginated (page/limit/offset/sort).",
        "operationId": "compareCities",
        "parameters": [
          {
            "in": "query",
            "name": "cities",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "berlin,koeln,hamburg",
            "description": "Kommaseparierte Stadt-Slugs (auf MAX_CITIES begrenzt).",
            "x-description-en": "Comma-separated city slugs (limited to MAX_CITIES)."
          },
          {
            "in": "query",
            "name": "resource",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "weather",
                "air",
                "indicators",
                "demographics",
                "unemployment",
                "tourism",
                "charging-status",
                "weather-warnings"
              ]
            },
            "description": "Zu vergleichende Ressource; unbekannt -> 400 invalid_request. weather/air = Live-Adapter (DWD/UBA); indicators, demographics, unemployment, tourism, charging-status und weather-warnings delegieren an die jeweiligen Stadt-Endpunkte (identisches Verhalten inkl. Attribution). charging-status liefert im Compare nur die Aggregate (points_omitted statt Einzelpunkt-Liste).",
            "x-description-en": "Resource to compare; unknown -> 400 invalid_request. weather/air = live adapters (DWD/UBA); indicators, demographics, unemployment, tourism, charging-status and weather-warnings delegate to the per-city endpoints (identical behaviour incl. attribution). charging-status returns aggregates only in compare (points_omitted instead of the point list)."
          },
          {
            "in": "query",
            "name": "page",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 50
            }
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string",
              "enum": [
                "city",
                "source_status"
              ]
            }
          },
          {
            "in": "query",
            "name": "order",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          },
          {
            "in": "header",
            "name": "If-None-Match",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Conditional GET; If-None-Match == ETag -> 304 Not Modified ohne Body.",
            "x-description-en": "Conditional GET; If-None-Match == ETag -> 304 Not Modified with no body."
          }
        ],
        "responses": {
          "200": {
            "description": "Vergleichsliste mit per-Stadt source_status",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/CacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "city",
                          "source_status"
                        ],
                        "properties": {
                          "city": {
                            "type": "string"
                          },
                          "data": {
                            "type": "object",
                            "nullable": true
                          },
                          "source_status": {
                            "type": "string",
                            "enum": [
                              "ok",
                              "disabled",
                              "no_data",
                              "error",
                              "not_found"
                            ]
                          }
                        }
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "description": "Unbekannte resource oder leeres cities (invalid_request)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/openapi.yaml": {
      "get": {
        "tags": [
          "meta"
        ],
        "summary": "Diese OpenAPI-Spec als YAML",
        "x-summary-en": "This OpenAPI spec as YAML",
        "description": "Liefert die handgepflegte Spec als application/yaml. FastAPI serviert intern nur /openapi.json; diese Route stellt die stabile YAML-Variante bereit.",
        "x-description-en": "Returns the hand-maintained spec as application/yaml. FastAPI internally serves only /openapi.json; this route provides the stable YAML variant.",
        "operationId": "getOpenapiYaml",
        "responses": {
          "200": {
            "description": "Die Spec als YAML",
            "content": {
              "application/yaml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Alle bekannten Städte auflisten",
        "x-summary-en": "List all known cities",
        "description": "Listet die Einträge des Stadt-Registers: 84 deutsche Großstädte über 100.000 Einwohner, davon 28 Kern-Städte mit voller Quellen-Abdeckung. Jeder Eintrag trägt den Slug, den du als {slug}-Pfadparameter in allen weiteren Endpunkten verwendest.",
        "x-description-en": "Lists the entries of the city register: 84 German cities with more than 100,000 inhabitants, 28 of them core cities with full source coverage. Each entry carries the slug you use as the {slug} path parameter in all other endpoints.",
        "operationId": "getCities",
        "responses": {
          "200": {
            "description": "Stadt-Liste",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Eine Stadt aus dem Register holen",
        "x-summary-en": "Get a single city from the register",
        "description": "Liefert den Register-Eintrag zur Stadt mit dem angegebenen {slug}. Ein unbekannter Slug löst den 404-Fehler-Envelope aus, dessen Hint auf die Stadt-Liste verweist.",
        "x-description-en": "Returns the register entry for the city with the given {slug}. An unknown slug triggers the 404 error envelope whose hint points to the city list.",
        "operationId": "getCity",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Stadt-Eintrag",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/base": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Stadt-Stammdaten aus Wikidata",
        "x-summary-en": "City master data from Wikidata",
        "description": "Liefert Einwohnerzahl, Fläche und Geo-Koordinaten einer Stadt aus Wikidata im kanonischen Envelope mit Attribution und Lizenz-Tag. Ist die Quelle deaktiviert, antwortet die Route mit 200, data=null und meta.source_status=\"disabled\" (nie 5xx). Ist der Upstream tot und liegt kein zwischengespeicherter Wert vor, kommt 503 mit einem Hint auf GET /api/v1/health.",
        "x-description-en": "Returns a city's population, area and geo coordinates from Wikidata in the canonical envelope with attribution and license tag. If the source is disabled, the route responds with 200, data=null and meta.source_status=\"disabled\" (never 5xx). If the upstream is down and no cached value exists, it returns 503 with a hint pointing to GET /api/v1/health.",
        "operationId": "getCityBase",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Stammdaten-Envelope. data ist der CanonicalRecord oder null, wenn die Quelle deaktiviert ist (meta.source_status=\"disabled\").",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Meta"
                        }
                      ],
                      "description": "meta trägt zusätzlich source_status (\"ok\"|\"disabled\") und auf dem ok-Pfad cache_status (HIT/MISS/STALE/STALE-ON-ERROR)."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/air": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Luftqualität je Stadt (UBA, Legacy-Pfad)",
        "x-summary-en": "Air quality per city (UBA, legacy path)",
        "description": "Aktuelle Luftqualitäts-Messwerte einer Stadt aus der Luftdaten-API des Umweltbundesamts im kanonischen Envelope. Die fünf Schadstoff-Messwerte (PM10, PM2.5, NO2, O3, SO2) stehen in data, Quelle und source_status in meta. Werte sind live (kein Zwischenbestand). Sonderfälle: deaktivierte Quelle liefert 200 mit data=null und source_status=\"disabled\"; toter Upstream ohne Cache liefert 503 mit Hint auf GET /api/v1/health.",
        "x-description-en": "Current air-quality readings for a city from the German Federal Environment Agency (UBA) air-data API in the canonical envelope. The five pollutant readings (PM10, PM2.5, NO2, O3, SO2) are in data, source and source_status in meta. Values are live (no intermediate store). Special cases: a disabled source returns 200 with data=null and source_status=\"disabled\"; a dead upstream with no cache returns 503 with a hint to GET /api/v1/health.",
        "operationId": "getCityAir",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Luftqualitäts-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/overview": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Stadt-Überblick (Katalog aller Datenarten + Live-Highlights)",
        "x-summary-en": "City overview (catalog of all data types + live highlights)",
        "description": "Ein-Aufruf-Überblick einer Stadt: Basisdaten, ein Katalog ALLER verfügbaren Datenarten (je mit Abdeckungsstatus und dem passenden MCP-Tool) sowie ein kleiner Live-Highlight-Snapshot (Wetter, Luft, Bahn-Abfahrten), parallel und zeitgedeckelt. Ideal als Einstieg, um die ganze Breite je Stadt zu sehen (nicht nur Wetter). Eine noch nicht abgedeckte Datenart wird ehrlich, aber vorwärts gewandt dargestellt (sie nennt die bereits abgedeckten Städte plus einen Roadmap-Hinweis, weil InfraNode laufend mehr Daten und Städte bekommt). Unbekannte Stadt liefert 404.",
        "x-description-en": "One-call overview of a city: base data, a catalog of ALL available data types (each with its coverage status and the matching MCP tool), and a small live highlights snapshot (weather + air), fetched in parallel and time-capped. Ideal as an entry point to see the full breadth per city, not just weather. A data type that is not yet covered is shown honestly but forward-looking (it lists the cities where it IS available plus a roadmap note, because InfraNode keeps adding more data types and cities). An unknown city returns 404.",
        "operationId": "getCityOverview",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Überblick-Envelope (Basis + Katalog + Highlights + Summary)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "city": {
                          "type": "object",
                          "description": "Basisdaten der Stadt (slug, Lage, Einwohner)."
                        },
                        "data_types": {
                          "type": "array",
                          "description": "Katalog aller Datenarten je mit type, label, label_en, tool (MCP-Tool), path und available; nicht abgedeckte tragen zusätzlich covered_cities und einen note-Hinweis.",
                          "items": {
                            "type": "object"
                          }
                        },
                        "highlights": {
                          "type": "object",
                          "description": "Live-Snapshot je Highlight-Quelle (weather, air) mit eigenem source_status (ok/no_data/disabled/error)."
                        },
                        "summary": {
                          "type": "object",
                          "description": "data_types_total, data_types_available, cities_total, eine headline (macht die verfügbare Breite in einem Satz greifbar) und ein Roadmap-Hinweis (note)."
                        }
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/weather": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Wetter je Stadt (DWD über Bright Sky)",
        "x-summary-en": "Weather per city (DWD via Bright Sky)",
        "description": "Aktuelle Wetter-Messwerte einer Stadt vom Deutschen Wetterdienst (über Bright Sky) im kanonischen Envelope. data trägt die Messwerte, attribution den GeoNutzV-Bearbeitungshinweis (modified=true). Sonderfälle: deaktivierte Quelle liefert 200 mit source_status=\"disabled\"; toter Upstream ohne Cache liefert 503 mit Hint auf GET /api/v1/health.",
        "x-description-en": "Current weather readings for a city from the German Weather Service (DWD, via Bright Sky) in the canonical envelope. data holds the readings, attribution carries the GeoNutzV modification notice (modified=true). Special cases: a disabled source returns 200 with source_status=\"disabled\"; a dead upstream with no cache returns 503 with a hint to GET /api/v1/health.",
        "operationId": "getCityWeather",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Wetter-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/pois": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "POIs je Stadt, nach Typ gefiltert (OSM, Tier B)",
        "x-summary-en": "POIs per city, filtered by type (OSM, Tier B)",
        "description": "Liefert POIs einer Stadt aus OpenStreetMap im kanonischen Envelope, gefiltert nach dem Query-Parameter type. Der Typ wird gegen eine feste Liste geprüft; ein unbekannter Typ löst 422 aus. Die Daten stammen aus einem periodischen Precompute (OSM-Deutschland-Extrakt, wöchentlich); die Route liest read-only aus dem Store, nicht live. total_available nennt den echten Gesamtbestand, die items-Liste ist eine gedeckelte, paginierbare Stichprobe. Tier B (copyleft, ODbL). Deaktiviert -> 200 source_status=\"disabled\"; noch kein Bestand -> 200 source_status=\"no_data\".",
        "x-description-en": "Returns a city's POIs from OpenStreetMap in the canonical envelope, filtered by the type query parameter. The type is checked against a fixed list; an unknown type triggers 422. Data comes from a periodic precompute (OSM Germany extract, weekly); the route reads read-only from the store, not live. total_available reports the real total, the items list is a capped, paginable sample. Tier B (copyleft, ODbL). Disabled -> 200 source_status=\"disabled\"; no data yet -> 200 source_status=\"no_data\".",
        "operationId": "getCityPois",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": true,
            "description": "POI-Typ aus der erlaubten Liste (z.B. hospital, school, pharmacy).",
            "x-description-en": "POI type from the allowed list (e.g. hospital, school, pharmacy).",
            "schema": {
              "type": "string",
              "example": "hospital"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "POI-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "Unbekannter POI-Typ (nicht in der Whitelist)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/playgrounds": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Spielplätze je Stadt (OSM, Tier B)",
        "x-summary-en": "Playgrounds per city (OSM, Tier B)",
        "description": "Öffentliche Spielplätze (OSM leisure=playground) im kanonischen Envelope. Tier B (copyleft, ODbL). Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Public playgrounds (OSM leisure=playground) in the canonical envelope. Tier B (copyleft, ODbL). Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityPlaygrounds",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OSM-Feature-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/drinking-water": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Trinkwasserbrunnen je Stadt (OSM, Tier B)",
        "x-summary-en": "Public drinking water fountains per city (OSM, Tier B)",
        "description": "Öffentliche Trinkwasserbrunnen (OSM amenity=drinking_water). Die OSM-Abdeckung ist je Stadt unterschiedlich vollständig. Tier B (ODbL). Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Public drinking water fountains (OSM amenity=drinking_water). OSM coverage varies per city. Tier B (ODbL). Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityDrinkingWater",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OSM-Feature-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/public-toilets": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Öffentliche Toiletten je Stadt (OSM, Tier B)",
        "x-summary-en": "Public toilets per city (OSM, Tier B)",
        "description": "Öffentliche Toiletten (OSM amenity=toilets) mit Barrierefreiheits-Tags je Element (wheelchair, changing_table) sowie fee/access/opening_hours/ unisex. Die OSM-Abdeckung ist je Stadt unterschiedlich vollständig. Tier B (ODbL). Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Public toilets (OSM amenity=toilets) with per-item accessibility tags (wheelchair, changing_table) plus fee/access/opening_hours/unisex. OSM coverage varies per city. Tier B (ODbL). Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityPublicToilets",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OSM-Feature-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/markets": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Wochenmärkte/Marktplätze je Stadt (OSM, Tier B)",
        "x-summary-en": "Markets per city (OSM, Tier B)",
        "description": "Marktplätze (OSM amenity=marketplace). Markttage/Zeiten kommen als optionales opening_hours je Element (häufig leer). Tier B (ODbL). Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Marketplaces (OSM amenity=marketplace). Market days/times come as optional opening_hours per element (often empty). Tier B (ODbL). Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityMarkets",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OSM-Feature-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/parcel-lockers": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Paketstationen je Stadt (OSM, Tier B)",
        "x-summary-en": "Parcel lockers per city (OSM, Tier B)",
        "description": "Paketstationen/Locker (OSM amenity=parcel_locker) mit optionalem operator/brand (DHL/Amazon/DPD/Hermes/GLS). Tier B (ODbL). Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Parcel lockers (OSM amenity=parcel_locker) with optional operator/brand (DHL/Amazon/DPD/Hermes/GLS). Tier B (ODbL). Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityParcelLockers",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OSM-Feature-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/post-offices": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Postfilialen je Stadt (OSM, Tier B)",
        "x-summary-en": "Post offices per city (OSM, Tier B)",
        "description": "Postfilialen (OSM amenity=post_office) mit optionalem opening_hours/operator. Tier B (ODbL). Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Post offices (OSM amenity=post_office) with optional opening_hours/operator. Tier B (ODbL). Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityPostOffices",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OSM-Feature-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/post-boxes": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Briefkästen je Stadt, mit Leerungszeiten (OSM, Tier B)",
        "x-summary-en": "Post boxes per city, with collection times (OSM, Tier B)",
        "description": "Öffentliche Briefkästen (OSM amenity=post_box). Leerungszeiten als optionales collection_times je Element (ca. drei Viertel getaggt; fehlend = Datenpunkt-Lücke). Tier B (ODbL). Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Public post boxes (OSM amenity=post_box). Collection times as optional collection_times per element (~75% tagged; missing = data gap). Tier B (ODbL). Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityPostBoxes",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OSM-Feature-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/public-wifi": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Öffentliche WLAN-Standorte je Stadt (OSM, Tier B)",
        "x-summary-en": "Public Wi-Fi locations per city (OSM, Tier B)",
        "description": "Öffentliche WLAN-Standorte (OSM internet_access=wlan) mit optionalem operator. Tier B (ODbL). Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Public Wi-Fi locations (OSM internet_access=wlan) with optional operator. Tier B (ODbL). Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityPublicWifi",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OSM-Feature-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/recycling-centres": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Recycling-/Wertstoffhöfe je Stadt (OSM, Tier B)",
        "x-summary-en": "Recycling centres per city (OSM, Tier B)",
        "description": "Recycling-/Wertstoffhöfe (OSM amenity=recycling + recycling_type=centre) mit optionalem opening_hours. Tier B (ODbL). Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Recycling centres (OSM amenity=recycling + recycling_type=centre) with optional opening_hours. Tier B (ODbL). Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityRecyclingCentres",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OSM-Feature-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/government-offices": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Behörden/Ämter je Stadt (OSM, Tier B)",
        "x-summary-en": "Government offices per city (OSM, Tier B)",
        "description": "Behörden/Ämter (OSM office=government + amenity=townhall), konsolidiert Bürgerämter, Verwaltungs- und sonstige Ämter. Subtyp je Element als optionales government-Tag. Tier B (ODbL). Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Government offices (OSM office=government + amenity=townhall), consolidating citizen, administrative and other offices. Subtype per element as optional government tag. Tier B (ODbL). Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityGovernmentOffices",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OSM-Feature-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/education": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Bildungseinrichtungen je Stadt (OSM, Tier B)",
        "x-summary-en": "Education facilities per city (OSM, Tier B)",
        "description": "Bildungseinrichtungen (OSM amenity=school/college/university/kindergarten). Tier B (ODbL). Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Education facilities (OSM amenity=school/college/university/kindergarten). Tier B (ODbL). Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityEducation",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OSM-Feature-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/population-density": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Einwohnerdichte je Stadt (Zensus-2022-Gitter, Tier A)",
        "x-summary-en": "Population density per city (Census 2022 grid, Tier A)",
        "description": "Einwohnerdichte einer Stadt aus dem amtlichen Zensus-2022-100m-Gitter, exakt aggregiert über die Gitterzellen mit der Stadt-AGS: Summe Einwohner, Zahl bewohnter 100m-Zellen, bewohnte Fläche (km²) und Dichte (Einwohner/km² über die bewohnte Fläche, nicht die Gesamtfläche). Flächendeckend (alle Städte); ohne Gitterzellen -> 200 source_status=\"no_data\". DL-DE/BY 2.0. Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "A city's population density from the official Census 2022 100m grid, aggregated exactly over the grid cells with the city's AGS: total inhabitants, populated 100m cells, populated area (km²) and density (inhabitants/km² over the populated area, not the total area). Full coverage (all cities); no grid cells -> 200 source_status=\"no_data\". DL-DE/BY 2.0. Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityPopulationDensity",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Einwohnerdichte-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/public-tenders": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Öffentliche Auftragsvergabe je Stadt (oeffentlichevergabe.de, Tier A)",
        "x-summary-en": "Public procurement notices per city (oeffentlichevergabe.de, Tier A)",
        "description": "Öffentliche Auftragsbekanntmachungen einer Stadt aus dem Bekanntmachungsdienst des Bundes (oeffentlichevergabe.de, OCDS): laufende Vergabeverfahren und entschiedene Verfahren, je Bekanntmachung Titel, Status, Auftraggeber, Wert und Quell-Link; bei entschiedenen Verfahren zusaetzlich die Auftragnehmer (suppliers, Namensliste; leer wenn die Quelle nicht offenlegt) und der tatsaechlich vergebene Auftragswert (award_value/award_currency, getrennt vom Ausschreibungswert value). status wird semantisch aus dem notice_type abgeleitet: active = laufendes Vergabeverfahren (Auftragsbekanntmachung), complete = entschiedenes Verfahren inkl. aufgehobener Vergaben; der rohe Zuschlag-Status steht im Feld award_status (active/pending/unsuccessful/null). Betraege <= 0 aus der Quelle werden als nicht angegeben (null) gewertet. Die Angebotsfrist (deadline) fehlt im Quell-Export fast immer und steht dann nur in der Original-Bekanntmachung über source_url. Read-only aus dem deduplizierten Store (kein Live-Pull im Request-Pfad). Filter status (active/complete) und match (buyer_city/place_of_performance), mit Pagination (limit/offset). Keine Bekanntmachungen -> 200 source_status=\"no_data\", data=null (ehrlich). CC0 (Tier A). Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "A city's public procurement notices from the German federal procurement publication service (oeffentlichevergabe.de, OCDS): running procedures and decided procedures, each with title, status, buyer, value and a source link; decided procedures additionally carry the contractors (suppliers, list of names; empty when the source does not disclose them) and the actually awarded value (award_value/award_currency, separate from the tender estimate value). status is derived semantically from the notice_type: active = running procedure (contract notice), complete = decided procedure incl. cancelled awards; the raw award status is in the award_status field (active/pending/unsuccessful/null). Amounts of 0 or less from the source are treated as not stated (null). The submission deadline is almost always missing in the source export and then only found in the original notice via source_url. Read-only from the deduplicated store (no live pull in the request path). Filter by status (active/complete) and match (buyer_city/place_of_performance), with pagination (limit/offset). No notices -> 200 source_status=\"no_data\", data=null (honest). CC0 (Tier A). Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityPublicTenders",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Verfahrens-Status (active = laufendes Vergabeverfahren, complete = entschiedenes Verfahren inkl. aufgehobener Vergaben; Detail im Feld award_status).",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "complete"
              ]
            }
          },
          {
            "name": "match",
            "in": "query",
            "required": false,
            "description": "Stadt-Zuordnung (buyer_city = Sitz des Amts, place_of_performance = Erfüllungsort).",
            "schema": {
              "type": "string",
              "enum": [
                "buyer_city",
                "place_of_performance"
              ]
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Titel-Stichwort (Teilstring, parametrisierter LIKE-Filter).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Nur Bekanntmachungen ab diesem ISO-Datum YYYY-MM-DD; ungültiges Datum -> 422.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Seitengröße (Default 50, Cap 200).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Seiten-Offset (>= 0); Overflow -> leere Seite, no_data.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Auftragsvergabe-Envelope (aggregierte notices-Liste + count, oder null bei no_data/disabled)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "type": "object",
                          "properties": {
                            "notices": {
                              "type": "array",
                              "items": {
                                "type": "object"
                              }
                            },
                            "count": {
                              "type": "integer"
                            },
                            "license_id": {
                              "type": "string",
                              "description": "Lizenz des Datensatzes (CC0 = Tier A)."
                            },
                            "license_tier": {
                              "type": "string"
                            },
                            "attribution": {
                              "type": "object",
                              "properties": {
                                "text": {
                                  "type": "string"
                                },
                                "license_url": {
                                  "type": "string"
                                }
                              }
                            }
                          }
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "Ungültiger Query-Parameter (status/match/q/since/limit/offset)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/council-papers": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Kommunale Ratsinformationen je Stadt (OParl, Tier A)",
        "x-summary-en": "Municipal council information per city (OParl, Tier A)",
        "description": "Kommunale Ratsinformationen einer Stadt aus dem OParl-1.x-Standard (OParl \"Paper\" = Vorlagen, Anträge, Beschlüsse): je Paper Titel, Referenz, Datum, Paper-Typ und der Link zur Hauptdatei (main_file_url, das PDF wird NICHT gespiegelt). Abgedeckt sind die acht lizenzgeklärten Städte Dresden, Köln, Düsseldorf, Münster, Leipzig, Magdeburg, Osnabrück und Freiburg im Breisgau (je eigene Lizenz in der attribution). Read-only aus dem deduplizierten Store (kein Live-Pull im Request-Pfad). Filter q (Titel-Stichwort), paper_type und since (ab ISO-Datum), mit Pagination (limit/offset); data.total nennt den Gesamtbestand zu den aktiven Filtern. Nicht abgedeckte Stadt -> 404 mit Hint. Keine Paper -> 200 source_status=\"no_data\", data=null. Deaktiviert -> 200 source_status=\"disabled\". Zusätzlich unter dem Alias /api/v1/cities/{slug}/council/papers erreichbar.",
        "x-description-en": "A city's municipal council information from the OParl 1.x standard (OParl \"Paper\" = council papers, motions, resolutions): each paper with title, reference, date, paper type and a link to the main file (main_file_url; the PDF is NOT mirrored). Covered are the eight licence-cleared cities Dresden, Cologne, Düsseldorf, Münster, Leipzig, Magdeburg, Osnabrück and Freiburg im Breisgau (each with its own licence in the attribution). Read-only from the deduplicated store (no live pull in the request path). Filter by q (title keyword), paper_type and since (from an ISO date), with pagination (limit/offset); data.total carries the full count for the active filters. Not-covered city -> 404 with hint. No papers -> 200 source_status=\"no_data\", data=null. Disabled -> 200 source_status=\"disabled\". Also available under the alias /api/v1/cities/{slug}/council/papers.",
        "operationId": "getCityCouncilPapers",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Titel-Stichwort (Teilstring, parametrisierter LIKE-Filter).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "paper_type",
            "in": "query",
            "required": false,
            "description": "Exakter Paper-Typ (z. B. Beschlussvorlage, Antrag).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Nur Paper ab diesem ISO-Datum YYYY-MM-DD; ungültiges Datum -> 422.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Seitengröße (Default 50, Cap 100).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Seiten-Offset (>= 0); Overflow -> leere Seite, no_data.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ratsinformations-Envelope (aggregierte papers-Liste + count, oder null bei no_data/disabled)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "type": "object",
                          "properties": {
                            "papers": {
                              "type": "array",
                              "items": {
                                "type": "object"
                              }
                            },
                            "count": {
                              "type": "integer"
                            },
                            "license_id": {
                              "type": "string",
                              "description": "Lizenz des Datensatzes je Stadt (Tier A)."
                            },
                            "license_tier": {
                              "type": "string"
                            },
                            "attribution": {
                              "type": "object",
                              "properties": {
                                "text": {
                                  "type": "string"
                                },
                                "license_url": {
                                  "type": "string"
                                },
                                "source": {
                                  "type": "string"
                                },
                                "modified": {
                                  "type": "boolean"
                                }
                              }
                            }
                          }
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte oder nicht abgedeckte Stadt (mit Hint)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "Ungültiger Query-Parameter (q/paper_type/since/limit/offset)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tenders": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Öffentliche Auftragsvergabe deutschlandweit (oeffentlichevergabe.de, Tier A)",
        "x-summary-en": "Public procurement notices nationwide (oeffentlichevergabe.de, Tier A)",
        "description": "Öffentliche Auftragsbekanntmachungen über ALLE Städte im Store aus dem Bekanntmachungsdienst des Bundes (oeffentlichevergabe.de, OCDS): sucht deutschlandweit, ohne jede Stadt einzeln abzufragen. Je Bekanntmachung Titel, Status, Auftraggeber, Wert und Quell-Link. status wird semantisch aus dem notice_type abgeleitet: active = laufendes Vergabeverfahren, complete = entschiedenes Verfahren inkl. aufgehobener Vergaben; der rohe Zuschlag-Status steht im Feld award_status. Entschiedene Verfahren tragen zusaetzlich Auftragnehmer (suppliers) und vergebenen Auftragswert (award_value/award_currency); Betraege <= 0 aus der Quelle werden als nicht angegeben (null) gewertet. Die Angebotsfrist (deadline) fehlt im Quell-Export fast immer und steht dann nur in der Original-Bekanntmachung über source_url. Read-only aus dem deduplizierten Store (kein Live-Pull im Request-Pfad). Filter q (Titel-Stichwort), status (active/complete) und since (ab ISO-Datum), mit Pagination (limit/offset). Keine Bekanntmachungen -> 200 source_status=\"no_data\", data=null (ehrlich). CC0 (Tier A). Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Public procurement notices across ALL cities in the store from the German federal procurement publication service (oeffentlichevergabe.de, OCDS): searches nationwide without querying each city individually. Each notice with title, status, buyer, value and a source link. status is derived semantically from the notice_type: active = running procedure, complete = decided procedure incl. cancelled awards; the raw award status is in the award_status field. Decided procedures additionally carry contractors (suppliers) and the awarded value (award_value/award_currency); amounts of 0 or less from the source are treated as not stated (null). The submission deadline is almost always missing in the source export and then only found in the original notice via source_url. Read-only from the deduplicated store (no live pull in the request path). Filter by q (title keyword), status (active/complete) and since (from ISO date), with pagination (limit/offset). No notices -> 200 source_status=\"no_data\", data=null (honest). CC0 (Tier A). Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getAllPublicTenders",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Titel-Stichwort (Teilstring, parametrisierter LIKE-Filter).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Verfahrens-Status (active = laufendes Vergabeverfahren, complete = entschiedenes Verfahren inkl. aufgehobener Vergaben; Detail im Feld award_status).",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "complete"
              ]
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Nur Bekanntmachungen ab diesem ISO-Datum YYYY-MM-DD; ungültiges Datum -> 422.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Seitengröße (Default 50, Cap 200).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Seiten-Offset (>= 0); Overflow -> leere Seite, no_data.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Auftragsvergabe-Envelope (aggregierte notices-Liste + count, oder null bei no_data/disabled)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "type": "object",
                          "properties": {
                            "notices": {
                              "type": "array",
                              "items": {
                                "type": "object"
                              }
                            },
                            "count": {
                              "type": "integer"
                            },
                            "license_id": {
                              "type": "string",
                              "description": "Lizenz des Datensatzes (CC0 = Tier A)."
                            },
                            "license_tier": {
                              "type": "string"
                            },
                            "attribution": {
                              "type": "object",
                              "properties": {
                                "text": {
                                  "type": "string"
                                },
                                "license_url": {
                                  "type": "string"
                                }
                              }
                            }
                          }
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Ungültiger Query-Parameter (status/q/since/limit/offset)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/tree-cadastre": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Baumkataster je Stadt (kommunaler WFS, Tier A)",
        "x-summary-en": "Street-tree cadastre per city (municipal WFS, Tier A)",
        "description": "Das städtische Baumkataster (Straßenbäume) aus dem kommunalen WFS, je Baum Punkt plus Attribute (Art, Pflanzjahr, Höhe, Straße, Bezirk). Die Attribute heißen kanonisch species, species_botanical, genus, planting_year und crown_diameter_m; die gleichwertigen rohen Quell-Feldnamen (z.B. art_dtsch, pflanzjahr, Baumart) sind abgekündigt und werden frühestens 30 Tage nach Ankündigung entfernt. Kataster sind sehr groß: die Antwort ist eine gedeckelte Stichprobe (count = ausgelieferte Bäume, nicht der Gesamtbestand). Teilabdeckung (kommunal, nur Städte mit verifiziertem offenem WFS, aktuell Berlin, DL-DE/Zero 2.0); andere Städte antworten mit 200 source_status=\"not_covered\" + meta.covered_cities. Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "A city's street-tree cadastre from the municipal WFS, each tree as a point plus attributes (species, planting year, height, street, district). The attributes are canonically named species, species_botanical, genus, planting_year and crown_diameter_m; the equivalent raw source field names (e.g. art_dtsch, pflanzjahr, Baumart) are deprecated and will be removed no earlier than 30 days after the announcement. Registers are very large: the response is a capped sample (count = returned trees, not the full stock). Partial coverage (municipal, only cities with a verified open WFS, currently Berlin, DL-DE/Zero 2.0); other cities return 200 source_status=\"not_covered\" + meta.covered_cities. Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityTreeCadastre",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Baumkataster-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/heritage": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Denkmäler/Baudenkmale je Stadt (Land-WFS, Tier A)",
        "x-summary-en": "Heritage/listed monuments per city (state WFS, Tier A)",
        "description": "Bau- und Denkmalobjekte einer Stadt aus der Denkmalliste des jeweiligen Bundeslandes (WFS), je Objekt ein Repräsentativpunkt plus Typ/Link. Die Attribute heißen kanonisch type, name, building_type, build_year, site_name, site_designation und publication_source; die gleichwertigen rohen Quell-Feldnamen (z.B. typ, bezeichnung, siteName) sind abgekündigt und werden frühestens 30 Tage nach Ankündigung entfernt. Denkmalschutz ist Ländersache: Teilabdeckung, nur Städte in Ländern mit verifiziertem offenem WFS (Berlin DL-DE/Zero 2.0; Hamburg, Baden-Württemberg, Hessen DL-DE/BY 2.0, Flächenländer per Stadt-Umkreis eingegrenzt). Andere Städte antworten ehrlich mit 200 source_status=\"not_covered\", data=null und meta.covered_cities. Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "A city's listed/heritage objects from the respective federal state's heritage register (WFS), each as a representative point plus type/link. The attributes are canonically named type, name, building_type, build_year, site_name, site_designation and publication_source; the equivalent raw source field names (e.g. typ, bezeichnung, siteName) are deprecated and will be removed no earlier than 30 days after the announcement. Heritage protection is a state matter: partial coverage, only cities in states with a verified open WFS (Berlin DL-DE/Zero 2.0; Hamburg, Baden-Württemberg, Hesse DL-DE/BY 2.0, area states narrowed to a city radius). Other cities honestly return 200 source_status=\"not_covered\", data=null and meta.covered_cities. Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityHeritage",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Denkmal-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/traffic": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Baustellen und Verkehrsmeldungen je Stadt (Autobahn-API)",
        "x-summary-en": "Roadworks and traffic warnings per city (Autobahn API)",
        "description": "Baustellen (roadworks) und Verkehrswarnungen (warnings) im Umfeld einer Stadt aus der Autobahn-API im kanonischen Envelope. Die Meldungen werden per Bounding-Box um die Stadt-Koordinaten gefiltert, data enthält die passenden Einträge. Flächendeckend: jeder der 84 Städte sind die nahen Autobahnen kuratiert zugeordnet (datengetrieben aus der Autobahn-API), eine Stadt ohne aktuelle Meldung antwortet ehrlich mit no_data. Stau/Verkehrslage: jede Verkehrswarnung trägt zusätzlich ein congestion-Feld (level stau/stockend/dicht/unspezifisch aus dem DATEX-abnormalTrafficType, delay_minutes = Reisezeitverlust, blocked), und payload.congestion_summary verdichtet die Stau-Lage je Stadt (count/stau/stockend/blocked/ max_delay_minutes; null wenn keine Stau-Meldung). Geometrie: die Antwort ist per Default schlank (die Roh-Polyline je Meldung ist weggelassen); mit include=geometry (oder ?full=1) bleibt das geometry-Feld in roadworks und warnings erhalten. Feldnamen: jedes Ereignis trägt die Werte zusätzlich in der API-Konvention (is_blocked als echtes Boolean, start_timestamp, delay_minutes, average_speed_kmh, abnormal_traffic_type, name, description_text sowie bbox/lat/lon aus extent); die camelCase-Rohfelder des Autobahn-Feeds sind abgekündigt und werden frühestens 30 Tage nach Ankündigung entfernt. Weitere Sonderfälle: deaktivierte Quelle liefert 200 mit source_status=\"disabled\"; toter Upstream ohne Cache liefert 503 mit Hint auf GET /api/v1/health.",
        "x-description-en": "Roadworks and traffic warnings around a city from the Autobahn API in the canonical envelope. Items are filtered by a bounding box around the city's coordinates; data contains the matching entries. Full coverage: each of the 84 cities has its nearby motorways curated (data-driven from the Autobahn API); a city with no current message honestly responds with no_data. Congestion: each traffic warning additionally carries a congestion field (level stau/stockend/dicht/unspezifisch derived from the DATEX abnormalTrafficType, delay_minutes = travel-time loss, blocked), and payload.congestion_summary aggregates the congestion situation per city (count/stau/stockend/blocked/max_delay_minutes; null if no congestion message). Field names: every item also carries its values in the API convention (is_blocked as a real boolean, start_timestamp, delay_minutes, average_speed_kmh, abnormal_traffic_type, name, description_text plus bbox/lat/lon derived from extent); the camelCase raw fields of the Autobahn feed are deprecated and will be removed no earlier than 30 days after the announcement. Other special cases: a disabled source returns 200 with source_status=\"disabled\"; a dead upstream with no cache returns 503 with a hint to GET /api/v1/health.",
        "operationId": "getCityTraffic",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Feld-Opt-in: include=geometry behält die Roh-Polyline (geometry) je roadwork/warning; Default ist schlank (geometry weggelassen).",
            "x-description-en": "Field opt-in: include=geometry keeps the raw polyline (geometry) per roadwork/warning; default is slim (geometry omitted).",
            "schema": {
              "type": "string",
              "enum": [
                "geometry"
              ]
            }
          },
          {
            "name": "full",
            "in": "query",
            "required": false,
            "description": "Äquivalentes Flag zu include=geometry (?full=1 behält die Polyline).",
            "x-description-en": "Equivalent flag to include=geometry (?full=1 keeps the polyline).",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Verkehrs-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/transit": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "ÖPNV-Haltestellen je Stadt (DELFI und HVV GTFS)",
        "x-summary-en": "Public-transit stops per city (DELFI and HVV GTFS)",
        "description": "Normalisierte ÖPNV-Haltestellen einer Stadt aus dem bundesweiten DELFI-GTFS-Feed bzw. dem HVV-Feed im kanonischen Envelope. Die Daten werden offline aufbereitet und als Snapshot je Stadt gehalten, sodass der Abruf schnell antwortet, ohne den Feed pro Request zu parsen. Sonderfälle: sind beide Quellen deaktiviert, kommt 200 mit source_status=\"disabled\"; liegt noch kein Snapshot vor, kommt 200 mit source_status=\"not_ingested\" und leeren data, nie ein 5xx. Mit q (Namenssuche) oder near (Umkreissuche) lassen sich gezielt einzelne Haltestellen finden, statt die Vollliste abzurufen.",
        "x-description-en": "Normalized public-transit stops for a city from the nationwide DELFI GTFS feed or the HVV feed in the canonical envelope. The data is prepared offline and kept as a per-city snapshot, so the request responds quickly without parsing the feed per request. Special cases: if both sources are disabled it returns 200 with source_status=\"disabled\"; if no snapshot exists yet it returns 200 with source_status=\"not_ingested\" and empty data, never a 5xx. Use q (name search) or near (proximity search) to find specific stops instead of fetching the full list.",
        "operationId": "getCityTransit",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Case-insensitiver Substring-Filter auf den Haltestellennamen (stop_name), z.B. alsterdorf.",
            "x-description-en": "Case-insensitive substring filter on the stop name (stop_name), e.g. alsterdorf.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "near",
            "in": "query",
            "required": false,
            "description": "\"lat,lon\"; liefert nur Haltestellen im Umkreis, aufsteigend nach Distanz sortiert. Ungültige Werte -> 400.",
            "x-description-en": "\"lat,lon\"; returns only stops within the radius, sorted by distance ascending. Invalid values -> 400.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "radius_m",
            "in": "query",
            "required": false,
            "description": "Suchradius für near in Metern (Default 1000).",
            "x-description-en": "Search radius for near in metres (default 1000).",
            "schema": {
              "type": "integer",
              "default": 1000
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Seitennummer der Default-Paginierung ohne q/near (Default 50 Einträge, Cap 200); meta.pagination weist total/returned/limit/offset/truncated aus.",
            "x-description-en": "Page number of the default pagination without q/near (default 50 entries, cap 200); meta.pagination reports total/returned/limit/offset/truncated.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Einträge pro Seite der Default-Paginierung ohne q/near (Default 50, Cap 200).",
            "x-description-en": "Entries per page of the default pagination without q/near (default 50, cap 200).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Transit-Envelope. data ist eine Liste von CanonicalRecord (TransitStopPayload) oder null/leer, je nach source_status (\"ok\"|\"disabled\"|\"not_ingested\").",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CanonicalRecord"
                          }
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ungültiger near-Parameter (kein \"lat,lon\")",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/charging": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "E-Ladesäulen-Standorte je Stadt (BNetzA, Tier A)",
        "x-summary-en": "EV charging-station locations per city (BNetzA, Tier A)",
        "description": "Liefert die E-Ladesäulen-Standorte einer Stadt (nur Stammdaten, KEINE Belegung) aus dem BNetzA-Ladesäulenregister im kanonischen Envelope. Die Daten stammen aus dem CSV-Bulk-Download der BNetzA (data.bundesnetzagentur.de) und werden periodisch aktualisiert; die Route liefert den jüngsten aufbereiteten Stand, keinen Live-Abruf. Deaktiviert -> 200 source_status=\"disabled\"; kein Snapshot -> 200 source_status=\"not_ingested\".",
        "x-description-en": "Returns a city's EV charging-station locations (master data only, NO occupancy) from the BNetzA charging-station register in the canonical envelope. The data comes from the BNetzA CSV bulk download (data.bundesnetzagentur.de) and is refreshed periodically; the route serves the most recent prepared state, not a live fetch. Disabled -> 200 source_status=\"disabled\"; no snapshot -> 200 source_status=\"not_ingested\".",
        "operationId": "getCityCharging",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ladesäulen-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/charging-status": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Live-Ladesäulen-Belegung je Stadt (eRound, CC0/Tier A)",
        "x-summary-en": "Live EV charging occupancy per city (eRound, CC0/Tier A)",
        "description": "Liefert die aggregierte Live-Ladesäulen-Belegung einer Stadt (eRound AFIR-Recharging, DATEX-II V3, CC0) im kanonischen Envelope. Join aus zwei Zuständen, kein Upstream-Abruf im Request: der statische eRound-Standort-Vollbestand liefert die Zuordnung Ladepunkt -> Stadt + Koordinaten (täglicher Ingest, alle 84 Städte abgedeckt), der dynamische Belegungs-Feed wird als Delta-Strom akkumuliert (Status je Ladepunkt, Staleness-Fenster 24 h). Payload: total_points (bekannte Ladepunkte im Stadtumkreis), reported_points (davon mit frischem Live-Status), status_counts (available/occupied/charging/unavailable u. a.) sowie die Einzelpunkte (refill_point_id, lat/lon, status, observed_at; Liste auf 500 gekappt, truncated markiert ehrlich). Quelle deaktiviert -> 200 source_status=\"disabled\"; noch kein akkumulierter Live-Status -> 200 source_status=\"no_data\".",
        "x-description-en": "Returns a city's aggregated live EV charging occupancy (eRound AFIR recharging, DATEX-II V3, CC0) in the canonical envelope. A join of two states with no upstream call at request time: the static eRound location inventory maps each charging point to a city + coordinates (daily ingest, all 84 cities covered), while the dynamic occupancy feed is accumulated as a delta stream (status per charging point, 24 h staleness window). Payload: total_points (known charging points within the city radius), reported_points (of which with a fresh live status), status_counts (available/occupied/charging/unavailable etc.) plus the individual points (refill_point_id, lat/lon, status, observed_at; list capped at 500, truncated flags honestly). Source disabled -> 200 source_status=\"disabled\"; no accumulated live status yet -> 200 source_status=\"no_data\".",
        "operationId": "getCityChargingStatus",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ladebelegungs-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/air-uba": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Luftqualität je Stadt (UBA, Tier A)",
        "x-summary-en": "Air quality per city (UBA, tier A)",
        "description": "Luftqualitäts-Messwerte einer Stadt aus der Luftdaten-API des Umweltbundesamts im kanonischen Envelope. Amtlicher Tier-A-Pfad für behördlich validierte Werte. Liefert dieselbe UBA-Quelle wie der ältere, nicht persistierte Pfad (/air). Sonderfälle: deaktivierte Quelle liefert 200 mit source_status=\"disabled\"; toter Upstream ohne Cache liefert 503 mit Hint auf GET /api/v1/health.",
        "x-description-en": "Air-quality readings for a city from the German Federal Environment Agency (UBA) air-data API in the canonical envelope. The official Tier-A path for authority-validated values. Serves the same UBA source as the older, non-persisted path (/air). Special cases: a disabled source returns 200 with source_status=\"disabled\"; a dead upstream with no cache returns 503 with a hint to GET /api/v1/health.",
        "operationId": "getCityAirUba",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Luftqualitäts-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/water-level": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Pegelstand je Stadt (PEGELONLINE, Tier A, Teilabdeckung)",
        "x-summary-en": "Water level per city (PEGELONLINE, Tier A, partial coverage)",
        "description": "Liefert den aktuellen Pegelstand der nächstgelegenen Station einer Stadt aus PEGELONLINE im kanonischen Envelope. Teilabdeckung: nur Städte an Bundeswasserstraßen haben eine Station, sonst kommt 200 mit source_status=\"no_data\" (kein 5xx). Tier A. Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Returns the current water level of the nearest station to a city from PEGELONLINE in the canonical envelope. Partial coverage: only cities on federal waterways have a station, otherwise it returns 200 with source_status=\"no_data\" (no 5xx). Tier A. Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityWaterLevel",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Pegelstand-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/flood": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Hochwasser-Warnstufen je Stadt (LHP, Tier A)",
        "x-summary-en": "Flood warning levels per city (LHP, Tier A)",
        "description": "Liefert die aktuellen Hochwasser-Warnstufen der kuratierten Pegel einer Stadt aus dem Länderübergreifenden Hochwasserportal (LHP) im kanonischen Envelope. Die Attribution trägt den \"Stand:\"-Zeitstempel (Pflicht). Der Payload-Zeitstempel heißt kanonisch as_of; das gleichwertige stand ist abgekündigt und wird frühestens 30 Tage nach Ankündigung entfernt. Tier A. Teilabdeckung (kuratierte Pegel je Stadt): nur berlin, bonn, dresden, duesseldorf, duisburg, erfurt, essen, frankfurt-am-main, hamburg, koeln, leipzig, mainz, muenchen, nuernberg liefern Daten. Andere registrierte Städte antworten ehrlich mit 200 source_status=\"not_covered\", data=null und meta.covered_cities (Liste der abgedeckten Städte). Weitere Sonderfälle: deaktiviert -> 200 source_status=\"disabled\"; toter Upstream ohne Cache -> 503 mit Hint.",
        "x-description-en": "Returns the current flood warning levels of a city's curated gauges from the German cross-state flood portal (LHP) in the canonical envelope. The attribution carries the mandatory \"Stand:\" timestamp. The payload timestamp is canonically named as_of; the equivalent stand is deprecated and will be removed no earlier than 30 days after the announcement. Tier A. Partial coverage (curated gauges per city): only berlin, bonn, dresden, duesseldorf, duisburg, erfurt, essen, frankfurt-am-main, hamburg, koeln, leipzig, mainz, muenchen, nuernberg return data. Other registered cities honestly respond with 200 source_status=\"not_covered\", data=null and meta.covered_cities (the list of covered cities). Other special cases: disabled -> 200 source_status=\"disabled\"; dead upstream with no cache -> 503 with a hint.",
        "operationId": "getCityFlood",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Hochwasser-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/pollen-uv": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Pollenflug und UV-Index je Stadt (DWD opendata, Tier A)",
        "x-summary-en": "Pollen and UV index per city (DWD opendata, Tier A)",
        "description": "Liefert Pollenflug-Gefahrenindex und UV-Index für die Großregion einer Stadt aus DWD opendata im kanonischen Envelope. Die Daten sind nach DWD-Großregionen gegliedert, NICHT stadtgenau (region_name im Payload). Attribution mit modified=true (GeoNutzV). Tier A. Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Returns the pollen hazard index and UV index for a city's macro-region from DWD opendata in the canonical envelope. The data is organized by DWD macro-regions, NOT city-precise (region_name in the payload). Attribution with modified=true (GeoNutzV). Tier A. Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityPollenUv",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Pollen-/UV-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/fire-danger": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Waldbrand- und Graslandfeuerindex je Stadt (DWD, Tier A)",
        "x-summary-en": "Forest-fire and grassland-fire danger index per city (DWD, Tier A)",
        "description": "Liefert den Waldbrandgefahrenindex (Stufe 1 sehr gering bis 5 sehr hoch) und den Graslandfeuerindex der nächsten DWD-Station zu einer Stadt im kanonischen Envelope. Der Index ist STATIONS-genau, NICHT stadtgenau (station_name und distance_km im Payload weisen die Station aus). Das Bundesland der Station heißt kanonisch federal_state; das gleichwertige bundesland ist abgekündigt und wird frühestens 30 Tage nach Ankündigung entfernt. Attribution mit modified=true (GeoNutzV). Tier A. Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Returns the forest-fire danger index (level 1 very low to 5 very high) and the grassland-fire index of the nearest DWD station to a city in the canonical envelope. The index is STATION-based, NOT city-precise (station_name and distance_km in the payload identify the station). The station's federal state is canonically named federal_state; the equivalent bundesland is deprecated and will be removed no earlier than 30 days after the announcement. Attribution with modified=true (GeoNutzV). Tier A. Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityFireDanger",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Waldbrand-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/bathing-water": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Badegewässerqualität im Umkreis einer Stadt (EEA, Tier A)",
        "x-summary-en": "Bathing water quality near a city (EEA, Tier A)",
        "description": "Liefert die Badegewässerqualität (EU-Badegewässerrichtlinie 2006/7/EG, EEA DiscoMap) im Umkreis einer Stadt im kanonischen Envelope. Je Badestelle Klasse (Excellent/Good/Sufficient/Poor), Gewässertyp, Koordinaten, Distanz und Profil-Link. ORTSNAH (Umland), NICHT stadtgenau (distance_km je Stelle); Inland-Städte ohne Badegewässer liefern count=0. CC-BY 4.0, Tier A. Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Returns bathing water quality (EU Bathing Water Directive 2006/7/EC, EEA DiscoMap) near a city in the canonical envelope. Per site: class (Excellent/Good/Sufficient/Poor), water category, coordinates, distance and profile link. NEARBY (surroundings), NOT city-precise (distance_km per site); inland cities without bathing waters return count=0. CC-BY 4.0, Tier A. Disabled -> 200 source_status=\"disabled\".",
        "operationId": "getCityBathingWater",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Badegewässer-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/hospitals-atlas": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Krankenhausstandorte je Stadt (Bundes-Klinik-Atlas, fail-closed)",
        "x-summary-en": "Hospital locations per city (Bundes-Klinik-Atlas, fail-closed)",
        "description": "Liefert standortgenaue Krankenhausdaten (Name, Adresse, Betten, Kontakt, Koordinaten) im Umkreis einer Stadt aus dem Bundes-Klinik-Atlas (BMG/IQTIG) im kanonischen Envelope. FAIL-CLOSED: keine explizite offene Lizenz ausgewiesen -> license_id UNKNOWN/Tier C und per Default DEAKTIVIERT (-> 200 source_status=\"disabled\"), bis BMG/IQTIG die Lizenz bestätigt. ORTSNAH gefiltert (distance_km je Standort).",
        "x-description-en": "Returns per-location hospital data (name, address, beds, contact, coordinates) near a city from the Bundes-Klinik-Atlas (BMG/IQTIG) in the canonical envelope. FAIL-CLOSED: no explicit open licence stated -> license_id UNKNOWN/Tier C and disabled by default (-> 200 source_status=\"disabled\") until BMG/IQTIG confirm the licence. Filtered by proximity (distance_km per location).",
        "operationId": "getCityHospitalsAtlas",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Krankenhausatlas-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/station-facilities": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Aufzug-/Rolltreppen-Status an Bahnhöfen je Stadt (DB FaSta, Tier A)",
        "x-summary-en": "Elevator/escalator status at stations per city (DB FaSta, Tier A)",
        "description": "Liefert den Echtzeit-Betriebsstatus von Aufzügen und Rolltreppen an Bahnhöfen einer Stadt (DB FaSta) im kanonischen Envelope. Je Anlage Typ (ELEVATOR/ESCALATOR), Status (ACTIVE/INACTIVE/UNKNOWN) + Begründung, Bahnhofsnummer, Koordinaten und Distanz. KEY-GATED: ohne DB-API-Schlüssel -> 200 source_status=\"disabled\". CC-BY 4.0, Tier A. ORTSNAH (Bahnhöfe im Stadtgebiet), distance_km je Anlage.",
        "x-description-en": "Returns the real-time operational state of elevators and escalators at a city's railway stations (DB FaSta) in the canonical envelope. Per facility: type (ELEVATOR/ESCALATOR), state (ACTIVE/INACTIVE/UNKNOWN) + reason, station number, coordinates and distance. KEY-GATED: without a DB API key -> 200 source_status=\"disabled\". CC-BY 4.0, Tier A.",
        "operationId": "getCityStationFacilities",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Aufzug-/Rolltreppen-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/demographics": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Demografie-Zeitreihen je Stadt (GENESIS/Regionalstatistik, Tier A)",
        "x-summary-en": "Demographic time series per city (GENESIS/Regionalstatistik, Tier A)",
        "description": "Liefert Demografie-Stammwerte und Zeitreihen einer Stadt aus der POST-API der GENESIS-/Regionalstatistik im kanonischen Envelope. Der Zugang ist account-gebunden: ohne hinterlegte Zugangsdaten oder bei deaktivierter Quelle antwortet der Endpunkt mit 200 source_status=\"disabled\". Tier A. Toter Upstream ohne Cache -> 503 mit Hint.",
        "x-description-en": "Returns a city's demographic base values and time series from the GENESIS/Regionalstatistik POST API in the canonical envelope. Access is account-bound: without stored credentials or with the source disabled the endpoint responds with 200 source_status=\"disabled\". Tier A. Dead upstream with no cache -> 503 with a hint.",
        "operationId": "getCityDemographics",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Demografie-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/energy": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Energie-Anlagen je Stadt (MaStR, Tier A)",
        "x-summary-en": "Energy installations per city (MaStR, Tier A)",
        "description": "Liefert die Energie-Anlagen (PV/Wind/Speicher/Biogas) einer Stadt aus dem offline aufbereiteten MaStR-SQLite-Store im kanonischen Envelope. Kein Upstream im Request-Pfad, der Abruf liest nur den vorbereiteten Bestand. Deaktiviert -> 200 source_status=\"disabled\". Batch noch nicht gelaufen -> 200 source_status=\"not_ingested\" (nie 5xx).",
        "x-description-en": "Returns a city's energy installations (PV/wind/storage/biogas) from the offline-prepared MaStR SQLite store in the canonical envelope. No upstream in the request path; the request only reads the prepared store. Disabled -> 200 source_status=\"disabled\". Batch not yet run -> 200 source_status=\"not_ingested\" (never 5xx).",
        "operationId": "getCityEnergy",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Energie-Envelope. data ist eine Liste von CanonicalRecord (EnergyAssetPayload) oder null/leer, je nach source_status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CanonicalRecord"
                          }
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/vehicle-registrations": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Pkw-Bestand und Elektro-Anteil je Stadt (KBA, Tier A)",
        "x-summary-en": "Car stock and electric share per city (KBA, Tier A)",
        "description": "Liefert den Pkw-Bestand und den Elektro-Anteil einer Stadt aus dem offline aufbereiteten KBA-SQLite-Store im kanonischen Envelope. Die regionale Auflösung ist der Zulassungsbezirk (= Kreis bzw. kreisfreie Stadt) und wird über district/district_key ehrlich ausgewiesen. Kein Upstream im Request-Pfad, der Abruf liest nur den vorbereiteten Bestand. Deaktiviert -> 200 source_status=\"disabled\". Batch noch nicht gelaufen -> 200 source_status=\"not_ingested\" (nie 5xx).",
        "x-description-en": "Returns a city's car stock and electric share from the offline-prepared KBA SQLite store in the canonical envelope. The spatial resolution is the registration district (= county or county-free city) and is honestly reported via district/district_key. No upstream in the request path; the request only reads the prepared store. Disabled -> 200 source_status=\"disabled\". Batch not yet run -> 200 source_status=\"not_ingested\" (never 5xx).",
        "operationId": "getCityVehicleRegistrations",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Fahrzeug-Envelope. data ist ein CanonicalRecord (VehicleRegistrationPayload) oder null, je nach source_status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/accidents": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Straßenverkehrsunfälle je Stadt (Unfallatlas, Tier A)",
        "x-summary-en": "Road traffic accidents per city (accident atlas, Tier A)",
        "description": "Liefert das jährliche Unfall-Aggregat einer Stadt aus dem offline aufbereiteten Unfallatlas-SQLite-Store im kanonischen Envelope: Gesamtzahl sowie Aufschlüsselung nach Unfallkategorie (Getötete/Schwer-/ Leichtverletzte) und beteiligter Verkehrsart (Rad/Fuß/Pkw/Krad). Regionale Auflösung Kreis/kreisfreie Stadt. Kein Upstream im Request-Pfad. Deaktiviert -> 200 source_status=\"disabled\". Batch noch nicht gelaufen -> 200 source_status=\"not_ingested\" (nie 5xx).",
        "x-description-en": "Returns a city's annual accident aggregate from the offline-prepared accident-atlas SQLite store in the canonical envelope: total plus a breakdown by accident category (fatal/serious/slight) and by participant type (bicycle/pedestrian/car/motorcycle). Spatial resolution county/ county-free city. No upstream in the request path. Disabled -> 200 source_status=\"disabled\". Batch not yet run -> 200 source_status=\"not_ingested\" (never 5xx).",
        "operationId": "getCityAccidents",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Unfall-Envelope (CanonicalRecord mit AccidentPayload oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/crime-stats": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Polizeiliche Kriminalstatistik je Stadt (BKA PKS, Tier A)",
        "x-summary-en": "Police crime statistics per city (BKA PKS, Tier A)",
        "description": "Liefert die polizeiliche Kriminalstatistik je Kreis aus dem offline aufbereiteten BKA-PKS-SQLite-Store im kanonischen Envelope: je Hauptstraftatengruppe (z.B. Straftaten insgesamt, Gewaltkriminalität, Wohnungseinbruchdiebstahl) die erfassten Fälle, die Häufigkeitszahl je 100.000 Einwohner und die Aufklärungsquote in Prozent, dazu Berichtsjahr (reference_year) und Version. Lizenz DL-DE/BY 2.0 (Tier A). Regionale Auflösung Kreis/kreisfreie Stadt. Kein Upstream im Request-Pfad. Sperr-/Leerwerte -> null. Deaktiviert -> 200 source_status=\"disabled\". Batch noch nicht gelaufen -> 200 source_status=\"not_ingested\" (nie 5xx).",
        "x-description-en": "Returns a city's police crime statistics per county from the offline- prepared BKA PKS SQLite store in the canonical envelope: per main offence group (e.g. total offences, violent crime, residential burglary) the recorded cases, the frequency per 100,000 inhabitants and the clearance rate in percent, plus the reporting year (reference_year) and version. Licence DL-DE/BY 2.0 (Tier A). Spatial resolution county/county-free city. No upstream in the request path. Blocked/empty values -> null. Disabled -> 200 source_status=\"disabled\". Batch not yet run -> 200 source_status=\"not_ingested\" (never 5xx).",
        "operationId": "getCityCrimeStats",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Kriminalstatistik-Envelope (CanonicalRecord mit CrimeStatsPayload oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/unemployment": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Arbeitslose und Arbeitslosenquote je Stadt (GENESIS, Tier A)",
        "x-summary-en": "Unemployment count and rate per city (GENESIS, Tier A)",
        "description": "Liefert Arbeitslose (Anzahl) und Arbeitslosenquote (Prozent) einer Stadt aus der Regionalstatistik (Arbeitsmarktstatistik der Bundesagentur für Arbeit, Jahresdurchschnitt) im kanonischen Envelope. Regionale Auflösung Kreis/kreisfreie Stadt (region_name). Quelle account-gated; deaktiviert -> 200 source_status=\"disabled\". Kein Wert -> 200 source_status=\"no_data\".",
        "x-description-en": "Returns a city's unemployment count and rate from the regional statistics (Federal Employment Agency, annual average) in the canonical envelope. Spatial resolution county/county-free city. Account-gated; disabled -> 200 source_status=\"disabled\". No value -> 200 source_status=\"no_data\".",
        "operationId": "getCityUnemployment",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Regionalstatistik-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/tourism": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Gästeübernachtungen und Ankünfte je Stadt (GENESIS, Tier A)",
        "x-summary-en": "Overnight stays and arrivals per city (GENESIS, Tier A)",
        "description": "Liefert Gästeübernachtungen und Gästeankünfte einer Stadt aus der Regionalstatistik (Monatserhebung im Tourismus, Jahressumme) im kanonischen Envelope. Regionale Auflösung Kreis/kreisfreie Stadt. Quelle account-gated; deaktiviert -> 200 source_status=\"disabled\". Kein Wert -> 200 source_status=\"no_data\".",
        "x-description-en": "Returns a city's overnight stays and guest arrivals from the regional statistics (tourism survey, annual sum) in the canonical envelope. Spatial resolution county/county-free city. Account-gated; disabled -> 200 source_status=\"disabled\". No value -> 200 source_status=\"no_data\".",
        "operationId": "getCityTourism",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Regionalstatistik-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/construction": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Baugenehmigungen je Stadt (GENESIS, Tier A)",
        "x-summary-en": "Building permits per city (GENESIS, Tier A)",
        "description": "Liefert genehmigte Wohngebäude und Wohnungen einer Stadt aus der Regionalstatistik (Statistik der Baugenehmigungen, Jahressumme) im kanonischen Envelope. Regionale Auflösung Kreis/kreisfreie Stadt. Quelle account-gated; deaktiviert -> 200 source_status=\"disabled\". Kein Wert -> 200 source_status=\"no_data\".",
        "x-description-en": "Returns a city's permitted residential buildings and dwellings from the regional statistics (building permit statistics, annual sum) in the canonical envelope. Spatial resolution county/county-free city. Account-gated; disabled -> 200 source_status=\"disabled\". No value -> 200 source_status=\"no_data\".",
        "operationId": "getCityConstruction",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Regionalstatistik-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/geo": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Verwaltungsgrenze je Stadt (BKG VG250, Tier A)",
        "x-summary-en": "Administrative boundary per city (BKG VG250, Tier A)",
        "description": "Liefert die Attributdaten der Verwaltungsgrenze (AGS, Gemeindename, Fläche) einer Stadt aus dem offline aufbereiteten BKG-VG250-Snapshot im kanonischen Envelope. Attribution wortgenau \"(c) GeoBasis-DE / BKG\". Deaktiviert -> 200 source_status=\"disabled\". Snapshot noch nicht gebaut -> 200 source_status=\"not_ingested\".",
        "x-description-en": "Returns the attribute data of a city's administrative boundary (AGS, municipality name, area) from the offline-prepared BKG VG250 snapshot in the canonical envelope. Attribution verbatim \"(c) GeoBasis-DE / BKG\". Disabled -> 200 source_status=\"disabled\". Snapshot not yet built -> 200 source_status=\"not_ingested\".",
        "operationId": "getCityGeo",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Geo-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/election": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Wahlergebnis je Stadt (Bundeswahlleiterin, Tier A)",
        "x-summary-en": "Election result per city (Federal Returning Officer, Tier A)",
        "description": "Liefert das Wahlergebnis je Wahlkreis/Kreis einer Stadt aus dem offline aufbereiteten Bundeswahl-Snapshot im kanonischen Envelope. Die Abdeckung wird ehrlich ausgewiesen: kanonisch als coverage_granularity mit den Werten \"city\"/\"partial\"; das gleichwertige granularity (Werte \"stadt\"/\"teilweise\") ist abgekündigt und wird frühestens 30 Tage nach Ankündigung entfernt. Deaktiviert -> 200 source_status=\"disabled\". Batch noch nicht gelaufen -> 200 source_status=\"not_ingested\".",
        "x-description-en": "Returns a city's election result per constituency/district from the offline-prepared federal-election snapshot in the canonical envelope. Coverage is reported honestly: canonically as coverage_granularity with the values \"city\"/\"partial\"; the equivalent granularity (values \"stadt\"/\"teilweise\") is deprecated and will be removed no earlier than 30 days after the announcement. Disabled -> 200 source_status=\"disabled\". Batch not yet run -> 200 source_status=\"not_ingested\".",
        "operationId": "getCityElection",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Wahl-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/holidays": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Feiertage und Schulferien je Stadt (Seed, gemeinfrei)",
        "x-summary-en": "Public holidays and school vacations per city (seed, public domain)",
        "description": "Liefert Feiertage und Schulferien je Bundesland (über entry.state aufgelöst) aus den eingebetteten Seed-Daten (data/seeds/) im kanonischen Envelope. Kein Upstream, gemeinfrei. Deaktiviert -> 200 source_status=\"disabled\". Kein Seed für das Bundesland -> 200 source_status=\"no_data\".",
        "x-description-en": "Returns public holidays and school vacations per federal state (resolved via entry.state) from the embedded seed data (data/seeds/) in the canonical envelope. No upstream, public domain. Disabled -> 200 source_status=\"disabled\". No seed for the state -> 200 source_status=\"no_data\".",
        "operationId": "getCityHolidays",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Feiertags-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/power-load": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Stromverbrauch (Netzlast) der Regelzone je Stadt (SMARD, Tier A)",
        "x-summary-en": "Electricity consumption (grid load) per city's control zone (SMARD, Tier A)",
        "description": "Realisierter Stromverbrauch (Netzlast) der Regelzone, in der die Stadt liegt (50Hertz/Amprion/TenneT/TransnetBW), als Tageswert in MWh. Regionale Kennzahl, nicht stadtgenau. Quelle: Bundesnetzagentur | SMARD.de (CC BY 4.0). Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Realised electricity consumption (grid load) of the control zone the city belongs to (50Hertz/Amprion/TenneT/TransnetBW) as a daily value in MWh. Regional metric, not city-exact. Source: Bundesnetzagentur | SMARD.de.",
        "operationId": "getCityPowerLoad",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Power-Envelope (CanonicalRecord mit PowerPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/power-price": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Day-ahead-Börsenstrompreis bundesweit (SMARD, Tier A)",
        "x-summary-en": "Day-ahead wholesale electricity price, nationwide (SMARD, Tier A)",
        "description": "Day-ahead-Großhandelspreis der Gebotszone DE/LU als Tageswert in EUR/MWh (bundesweit, für alle Städte identisch). Quelle: Bundesnetzagentur | SMARD.de (CC BY 4.0). Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Day-ahead wholesale electricity price of the DE/LU bidding zone as a daily value in EUR/MWh (nationwide, identical for all cities). Source: Bundesnetzagentur | SMARD.de.",
        "operationId": "getCityPowerPrice",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Power-Envelope (CanonicalRecord mit PowerPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/weather-warnings": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Amtliche DWD-Wetterwarnungen je Stadt (Tier A)",
        "x-summary-en": "Official DWD weather warnings per city (Tier A)",
        "description": "Aktive amtliche Wetterwarnungen des Deutschen Wetterdienstes für die Stadt, bezogen über die keylose Bright-Sky-Alerts-API (Stadt-Zuordnung per Koordinaten aus dem Städte-Register). Liefert max_level (0 = keine reguläre Warnung, 1-4 = Warnstufe aus der CAP-severity), count und die Einzelwarnungen (start/end als ISO-8601-Zeitstempel). Hitze- und UV-Gesundheitswarnungen zählen NICHT in max_level, sondern stehen separat in special_warnings. Quelle: Deutscher Wetterdienst (GeoNutzV). Deaktiviert -> 200 source_status=\"disabled\".",
        "x-description-en": "Active official weather warnings from the German Weather Service (DWD) for the city, fetched via the keyless Bright Sky alerts API (city matched by coordinates from the city registry). Returns max_level (0 = none, 1-4 = level derived from the CAP severity), count and the individual warnings (start/end as ISO 8601 timestamps). Heat and UV health warnings do NOT count towards max_level and are listed separately in special_warnings. Source: Deutscher Wetterdienst (GeoNutzV).",
        "operationId": "getCityWeatherWarnings",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Warnungen-Envelope (CanonicalRecord mit WeatherWarningPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/civil-protection-warnings": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Amtliche BBK-NINA-Bevölkerungsschutz-Warnungen je Stadt (Tier A)",
        "x-summary-en": "Official BBK NINA civil protection warnings per city (Tier A)",
        "description": "Aktive amtliche Bevölkerungsschutz-Warnungen des Bundesamts für Bevölkerungsschutz und Katastrophenhilfe (BBK, NINA-API) für die Stadt. Füllt die Zivilschutz-Lücke (Gefahrstoff, Großbrand, Bombenentschärfung) neben weather-warnings und flood. Der Regionsbezug erfolgt über den 12-stelligen Kreis-ARS (aus dem AGS abgeleitet); coverage_granularity (city|district) weist die ARS-Kreisschärfe aus. Der amtliche Warntext (headline) wird UNVERÄNDERT, verbatim durchgereicht (§ 5 Abs. 2 UrhG, keine Umformulierung/Kürzung/Übersetzung); je Warnung stehen provider (MOWAS/KATWARN/BIWAPP/POLICE/DWD/LHP), severity, sent, onset, expires, detail_url und duplicate_of (DWD -> weather-warnings, LHP -> flood). Quelle: BBK NINA, keylos. Deaktiviert -> 200 source_status=\"disabled\"; keine aktive Warnung -> 200 source_status=\"ok\" (count 0).",
        "x-description-en": "Active official civil protection warnings from the German Federal Office of Civil Protection and Disaster Assistance (BBK, NINA API) for the city. Fills the civil-protection gap (hazmat, major fire, bomb disposal) alongside weather-warnings and flood. Region mapping uses the 12-digit district ARS (derived from the AGS); coverage_granularity (city|district) states the ARS granularity. The official warning text (headline) is passed through VERBATIM, unchanged (§ 5 (2) UrhG, no rewording/shortening/ translation); each warning carries provider (MOWAS/KATWARN/BIWAPP/POLICE/ DWD/LHP), severity, sent, onset, expires, detail_url and duplicate_of (DWD -> weather-warnings, LHP -> flood). Source: BBK NINA, keyless.",
        "operationId": "getCityCivilProtectionWarnings",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Warnungen-Envelope (CanonicalRecord mit CivilProtectionWarningPayload: ars, coverage_granularity, count, warnings) oder null bei source_status disabled. source_status ok/disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Upstream (BBK NINA) nicht erreichbar, kein Cache",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/fuel-prices": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Aktuelle Spritpreise je Stadt, aggregiert (Tankerkönig/MTS-K, Tier A)",
        "x-summary-en": "Current fuel prices per city, aggregated (Tankerkönig/MTS-K, Tier A)",
        "description": "Aktuelle Spritpreise im Umkreis der Stadtkoordinate, aggregiert zu einer Stadt-Kennzahl: Durchschnitts- und Minimal-Preis je Sorte (e5/e10/diesel, EUR/Liter) über die geöffneten Tankstellen, plus station_count/open_count und die Einzel-Tankstellen (stations: je Tankstelle station_id, name, brand, e5, e10, diesel, is_open, dist_km, lat/lon für die Kartendarstellung sowie street, house_number, post_code, place als gemeldete Adresse; post_code ist immer ein fünfstelliger String. Meldet die Quelle keine eigene Hausnummer, wird eine im Straßennamen enthaltene Nummer abgetrennt; Tankstellen ohne Hausnummer behalten house_number null. Fehlt eine Angabe upstream, ist das Feld null). Quelle: Markttransparenzstelle für Kraftstoffe (MTS-K) via Tankerkönig (CC BY 4.0). Ohne API-Key ODER Toggle aus -> 200 source_status=\"disabled\"; keine Tankstelle im Radius -> 200 source_status=\"no_data\".",
        "x-description-en": "Current fuel prices around the city coordinate, aggregated into a city-level metric: average and minimum price per fuel grade (e5/e10/diesel, EUR/litre) across the open stations, plus station_count/open_count and the individual stations (stations: each carries station_id, name, brand, e5, e10, diesel, is_open, dist_km, lat/lon for map display plus street, house_number, post_code, place as the reported address; post_code is always a five-character string. When the source reports no separate house number, a number contained in the street name is split off; stations without a house number keep house_number null. A field is null when the upstream record omits it). Source: German fuel price transparency unit (MTS-K) via Tankerkönig (CC BY 4.0). Without an API key OR toggle off -> 200 source_status=\"disabled\"; no station in radius -> 200 source_status=\"no_data\".",
        "operationId": "getCityFuelPrices",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Spritpreis-Envelope (CanonicalRecord mit FuelPricePayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/stations": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Bahnhofs-Katalog einer Stadt, alle DB-Bahnhöfe (StaDa, Tier A)",
        "x-summary-en": "City station catalog, all DB stations (StaDa, Tier A)",
        "description": "Liste ALLER DB-Bahnhöfe im Stadtgebiet (Zuordnung über den amtlichen Gemeindeschlüssel: StaDa municipalityCode == Stadt-ags), nicht nur der Fernverkehrs-Hbf. Je Bahnhof eva (EVA-Nummer, füttert GET /stations/{eva}/departures), name, station_category (1-7, das gleichwertige category ist abgekuendigt), lat, lon, post_code (fuenfstelliger String; das gleichwertige zip ist abgekuendigt). Quelle: Deutsche Bahn AG / StaDa (CC BY 4.0). Ohne Zugangsdaten ODER Toggle aus -> 200 source_status=\"disabled\"; kein DB-Bahnhof im Stadtgebiet -> 200 source_status=\"no_data\". Volle Abdeckung (alle Städte).",
        "x-description-en": "List of ALL DB stations within the city (mapped via the official municipality key: StaDa municipalityCode == city ags), not just the long-distance main station. Each carries eva (EVA number, feeds GET /stations/{eva}/departures), name, category (1-7), lat, lon, zip. Source: Deutsche Bahn AG / StaDa (CC BY 4.0). Without credentials OR toggle off -> 200 source_status=\"disabled\"; no DB station in the city -> 200 source_status=\"no_data\". Full coverage (all cities).",
        "operationId": "getCityStations",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Optionaler Namensfilter: umlaut-toleranter Teilstring-Match auf dem Bahnhofsnamen (munchen UND muenchen matchen \"München Hbf\"). Kein Treffer -> 200 source_status=\"no_data\".",
            "x-description-en": "Optional name filter: umlaut-tolerant substring match on the station name (both munchen and muenchen match \"München Hbf\"). No match -> 200 source_status=\"no_data\".",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Kappt die sortierte Liste (Default: alle Bahnhöfe). Werte über 100 werden auf 100 geklemmt; nicht-numerisch oder kleiner 1 -> 422.",
            "x-description-en": "Caps the sorted list (default: all stations). Values above 100 are clamped to 100; non-numeric or below 1 -> 422.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Katalog-Envelope (CanonicalRecord mit StationCatalogPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/station-departures": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Live-Abfahrtstafel Haupt-Bahnhof der Stadt, alle Gattungen (DB Timetables, Tier A)",
        "x-summary-en": "Live departure board for a city's main station, all categories (DB Timetables, Tier A)",
        "description": "Nächste Zugabfahrten am Haupt-Bahnhof der Stadt mit Echtzeit-Verspätung: Sollfahrplan (/plan) und aktuelle Abweichungen (/fchg) der DB-Timetables-API, je Bahnhof zu einer nach Abfahrt sortierten Liste zusammengeführt (line, category, destination, planned_time mit Zeitzone, platform, delay_minutes, cancelled, long_distance). trip_stop_id bezeichnet EINEN HALT EINER ZUGFAHRT und ist KEINE Haltestellen-ID; fuer /api/v1/live/{slug}/transit/departures wird die DELFI-ID aus /api/v1/cities/{slug}/transit gebraucht. Das gleichwertige stop_id ist abgekuendigt. Volle Abdeckung über alle 84 Städte: der Haupt-Bahnhof wird automatisch aus dem amtlichen StaDa-Katalog abgeleitet (für die großen Knoten eine verifizierte Auswahl). Quelle: Deutsche Bahn AG (CC BY 4.0). Ohne Zugangsdaten ODER Toggle aus -> 200 source_status=\"disabled\"; keine Abfahrt im Zeitfenster -> 200 source_status=\"no_data\". Ein einzelner gestörter Bahnhof wird übersprungen und die Tafel aus den erreichbaren Bahnhöfen gebaut; erst wenn alle Bahnhöfe gestört sind und kein Cache vorliegt -> 503 upstream_unavailable. Für einen bestimmten Bahnhof statt der Stadt-Auswahl dient /api/v1/stations/{eva}/departures.",
        "x-description-en": "Upcoming train departures at a city's main station with real-time delays: scheduled timetable (/plan) and current changes (/fchg) of the DB Timetables API, merged per station into a list sorted by departure (line, category, destination, planned_time, platform, delay_minutes, cancelled, long_distance). Full coverage across all 84 cities: the main station is derived automatically from the official StaDa catalog (a verified pick for the major hubs). Source: Deutsche Bahn AG (CC BY 4.0). Without credentials OR toggle off -> 200 source_status=\"disabled\"; no departure in the window -> 200 source_status=\"no_data\". A single disrupted station is skipped and the board is built from the reachable stations; only if every station fails and no cache is available -> 503 upstream_unavailable. To target a specific station instead of the city's pick, use /api/v1/stations/{eva}/departures.",
        "operationId": "getCityStationDepartures",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Abfahrts-Envelope (CanonicalRecord mit StationDeparturesPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/station-arrivals": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Live-Ankunftstafel Haupt-Bahnhof der Stadt, alle Gattungen (DB Timetables, Tier A)",
        "x-summary-en": "Live arrival board for a city's main station, all categories (DB Timetables, Tier A)",
        "description": "Spiegelbild zu station-departures: ankommende Züge am Haupt-Bahnhof der Stadt mit Echtzeit-Verspätung (Soll /plan + Änderungen /fchg gemerged), je Eintrag mit origin (Startbahnhof), line, category, planned_time, platform, delay_minutes, cancelled, long_distance. Volle Abdeckung über alle 84 Städte: der Haupt-Bahnhof wird automatisch aus dem amtlichen StaDa-Katalog abgeleitet. Quelle: Deutsche Bahn AG (CC BY 4.0). Ohne Zugangsdaten ODER Toggle aus -> 200 source_status=\"disabled\"; keine Ankunft im Zeitfenster -> 200 source_status=\"no_data\". Für einen bestimmten Bahnhof statt der Stadt-Auswahl dient /api/v1/stations/{eva}/arrivals.",
        "x-description-en": "Mirror of station-departures: incoming trains at a city's main station with real-time delays (scheduled /plan + changes /fchg merged), each with origin (start station), line, category, planned_time, platform, delay_minutes, cancelled, long_distance. Full coverage across all 84 cities: the main station is derived automatically from the official StaDa catalog. Source: Deutsche Bahn AG (CC BY 4.0). Without credentials OR toggle off -> 200 source_status=\"disabled\"; no arrival in the window -> 200 source_status=\"no_data\". To target a specific station instead of the city's pick, use /api/v1/stations/{eva}/arrivals.",
        "operationId": "getCityStationArrivals",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ankunfts-Envelope (CanonicalRecord mit StationArrivalsPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/stations/{eva}/departures": {
      "get": {
        "tags": [
          "stations"
        ],
        "summary": "Live-Abfahrten eines beliebigen Bahnhofs per EVA, alle Gattungen (DB Timetables, Tier A)",
        "x-summary-en": "Live departures for any station by EVA, all categories (DB Timetables, Tier A)",
        "description": "Live-Abfahrtstafel JEDES DB-Bahnhofs über seine EVA-Nummer (aus GET /cities/{slug}/stations), inkl. Nahverkehr (alle Gattungen ICE/IC/RE/RB/S), Echtzeit-Verspätung und Störungen/Meldungen (messages je Eintrag). Quelle: Deutsche Bahn AG (CC BY 4.0). Ohne Zugangsdaten ODER Toggle aus -> 200 source_status=\"disabled\"; keine Abfahrt im Zeitfenster -> 200 source_status=\"no_data\"; ungültige EVA -> 422.",
        "x-description-en": "Live departure board for ANY DB station via its EVA number (from GET /cities/{slug}/stations), including local/regional trains (all categories ICE/IC/RE/RB/S), real-time delays and disruption messages (messages per entry). Source: Deutsche Bahn AG (CC BY 4.0). Without credentials OR toggle off -> 200 source_status=\"disabled\"; no departure in the window -> 200 source_status=\"no_data\"; invalid EVA -> 422.",
        "operationId": "getStationDepartures",
        "parameters": [
          {
            "name": "eva",
            "in": "path",
            "required": true,
            "description": "EVA-Nummer des Bahnhofs (6-8-stellige Zahl).",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{6,8}$",
              "example": "8000105"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Abfahrts-Envelope (CanonicalRecord mit StationDeparturesPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Ungueltige EVA-Nummer",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/stations/{eva}/arrivals": {
      "get": {
        "tags": [
          "stations"
        ],
        "summary": "Live-Ankünfte eines beliebigen Bahnhofs per EVA, alle Gattungen (DB Timetables, Tier A)",
        "x-summary-en": "Live arrivals for any station by EVA, all categories (DB Timetables, Tier A)",
        "description": "Spiegelbild zu /stations/{eva}/departures: ankommende Züge jedes DB- Bahnhofs über seine EVA-Nummer (inkl. Nahverkehr, origin = Startbahnhof, Echtzeit-Verspätung, Störungen/Meldungen). Quelle: Deutsche Bahn AG (CC BY 4.0). Ohne Zugangsdaten ODER Toggle aus -> 200 source_status=\"disabled\"; keine Ankunft im Zeitfenster -> 200 source_status=\"no_data\"; ungültige EVA -> 422.",
        "x-description-en": "Mirror of /stations/{eva}/departures: incoming trains for any DB station via its EVA number (incl. local/regional, origin = start station, real-time delays, disruption messages). Source: Deutsche Bahn AG (CC BY 4.0). Without credentials OR toggle off -> 200 source_status=\"disabled\"; no arrival in the window -> 200 source_status=\"no_data\"; invalid EVA -> 422.",
        "operationId": "getStationArrivals",
        "parameters": [
          {
            "name": "eva",
            "in": "path",
            "required": true,
            "description": "EVA-Nummer des Bahnhofs (6-8-stellige Zahl).",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{6,8}$",
              "example": "8000105"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ankunfts-Envelope (CanonicalRecord mit StationArrivalsPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Ungueltige EVA-Nummer",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/indicators": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Sozialökonomische Indikatoren je Stadt (INKAR/BBSR, Tier A)",
        "x-summary-en": "Socioeconomic indicators per city (INKAR/BBSR, Tier A)",
        "description": "Kuratiertes, breites Set sozialökonomischer Indikatoren je Kreis/ kreisfreie Stadt (Arbeitsmarkt, Wirtschaft, Einkommen, Demografie, Wohnen, Erreichbarkeit, Verkehr, Bildung, Gesundheit, Fläche), aus der offenen INKAR-Wizard-API des BBSR. Je Indikator der jüngste Jahreswert mit Bezeichnung (inkl. Einheit), Wert, Jahr und Kategorie. Quelle: BBSR / INKAR (DL-DE/BY 2.0). Toggle aus -> 200 source_status=\"disabled\"; kein Snapshot für den Kreis -> 200 source_status=\"not_ingested\".",
        "x-description-en": "A curated, broad set of socioeconomic indicators per district/independent city (labour market, economy, income, demographics, housing, accessibility, transport, education, health, land use), from the open INKAR wizard API of the BBSR. Each indicator carries its latest annual value with label (incl. unit), value, year and category. Source: BBSR / INKAR (DL-DE/BY 2.0). Toggle off -> 200 source_status=\"disabled\"; no snapshot for the district -> 200 source_status=\"not_ingested\".",
        "operationId": "getCityIndicators",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Indikatoren-Envelope (CanonicalRecord mit IndicatorsPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/sustainability": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Nachhaltigkeits- und SDG-Indikatoren als Zeitreihe (Wegweiser, Tier A)",
        "x-summary-en": "Sustainability and SDG indicators as a time series (Wegweiser, Tier A)",
        "description": "Kommunale Nachhaltigkeits- und SDG-Indikatoren je Gemeinde aus dem Wegweiser Kommune der Bertelsmann Stiftung (CC0): Flächeninanspruchnahme, Naherholungsflächen, erneuerbare Energie im Wohnungsneubau, Breitbandversorgung, Beschäftigung, Bildung, soziale Teilhabe und weitere. Anders als /indicators (INKAR, jüngster Wert) trägt jeder Indikator hier die ganze ZEITREIHE, in der Regel 2006 bis 2023, dazu latest_year/latest_value für den jüngsten Punkt. Jahre ohne Wert fehlen in der Reihe (nie eine erfundene Null). 53 Indikatoren liegen erst ab Kreisebene vor und fehlen deshalb bei kreisangehörigen Städten. Toggle aus -> 200 source_status=\"disabled\"; kein Snapshot -> 200 source_status=\"not_ingested\".",
        "x-description-en": "Municipal sustainability and SDG indicators per municipality from Wegweiser Kommune by Bertelsmann Stiftung (CC0): land take, local recreation areas, renewable energy in new residential buildings, broadband coverage, employment, education, social participation and more. Unlike /indicators (INKAR, latest value only), every indicator here carries the full TIME SERIES, typically 2006 to 2023, plus latest_year/latest_value for the most recent point. Years without a value are absent from the series (never an invented zero). 53 indicators are only available from district level and are therefore missing for municipalities that are part of a district. Toggle off -> 200 source_status=\"disabled\"; no snapshot -> 200 source_status=\"not_ingested\".",
        "operationId": "getCitySustainability",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Zeitreihen-Envelope (CanonicalRecord mit IndicatorSeriesPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/population-structure": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Altersaufbau der Bevoelkerung als Zeitreihe (Wegweiser, Tier A)",
        "x-summary-en": "Population age structure as a time series (Wegweiser, Tier A)",
        "description": "110 Indikatoren zum Altersaufbau je Stadt aus dem Wegweiser Kommune (CC0): Zahl und Anteil je Altersgruppe (0-2 bis ab 80), nach Geschlecht, nach Generationen, Altenquotient und Jugendquotient. Ist-Daten ab 2006, Prognosewerte bis 2040. Ungefiltert wiegt die Antwort rund 90 KB; fuer einen einzelnen Stand lohnt ?from=2023&to=2023. Jede Kennzahl traegt ihre ZEITREIHE mit latest_year und latest_value; Jahre ohne Wert fehlen in der Reihe (nie eine erfundene Null). Toggle aus -> 200 source_status=\"disabled\"; kein Snapshot oder leeres Jahresfenster -> 200 source_status=\"not_ingested\".",
        "x-description-en": "110 indicators on the age structure per city from Wegweiser Kommune (CC0): count and share per age group (0-2 up to 80+), by gender, by generation, old-age and youth dependency ratio. Actuals from 2006, forecast values to 2040. Unfiltered the response weighs about 90 KB; for a single snapshot use ?from=2023&to=2023. Every metric carries its TIME SERIES plus latest_year and latest_value; years without a value are absent from the series (never an invented zero). Toggle off -> 200 source_status=\"disabled\"; no snapshot or an empty year window -> 200 source_status=\"not_ingested\".",
        "operationId": "getCityPopulationStructure",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Erstes Jahr der Reihe (inklusive, 2006-2040). Ohne Angabe kommt die volle Reihe. Keine Zahl oder ausserhalb des Bestands -> 400.",
            "schema": {
              "type": "integer",
              "minimum": 2006,
              "maximum": 2040
            },
            "example": 2020
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Letztes Jahr der Reihe (inklusive, 2006-2040). Muss groesser oder gleich from sein, sonst 400.",
            "schema": {
              "type": "integer",
              "minimum": 2006,
              "maximum": 2040
            },
            "example": 2023
          }
        ],
        "responses": {
          "200": {
            "description": "Zeitreihen-Envelope (CanonicalRecord mit IndicatorSeriesPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ungueltige Jahresangabe in from oder to",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/population-trend": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Bevoelkerungsentwicklung als Zeitreihe (Wegweiser, Tier A)",
        "x-summary-en": "Population trend as a time series (Wegweiser, Tier A)",
        "description": "70 Indikatoren zur Veraenderung statt zum Bestand: Entwicklung der Altersgruppen (absolut und seit 2011, auch nach Geschlecht), Geburten- und Sterberate, Wanderungssaldo, Gesamtbevoelkerungsentwicklung. Jede Kennzahl traegt ihre ZEITREIHE mit latest_year und latest_value; Jahre ohne Wert fehlen in der Reihe (nie eine erfundene Null). Toggle aus -> 200 source_status=\"disabled\"; kein Snapshot oder leeres Jahresfenster -> 200 source_status=\"not_ingested\".",
        "x-description-en": "70 indicators on change rather than stock: development of age groups (absolute and since 2011, also by gender), birth and death rates, migration balance, overall population development. Every metric carries its TIME SERIES plus latest_year and latest_value; years without a value are absent from the series (never an invented zero). Toggle off -> 200 source_status=\"disabled\"; no snapshot or an empty year window -> 200 source_status=\"not_ingested\".",
        "operationId": "getCityPopulationTrend",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Erstes Jahr der Reihe (inklusive, 2006-2040). Ohne Angabe kommt die volle Reihe. Keine Zahl oder ausserhalb des Bestands -> 400.",
            "schema": {
              "type": "integer",
              "minimum": 2006,
              "maximum": 2040
            },
            "example": 2020
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Letztes Jahr der Reihe (inklusive, 2006-2040). Muss groesser oder gleich from sein, sonst 400.",
            "schema": {
              "type": "integer",
              "minimum": 2006,
              "maximum": 2040
            },
            "example": 2023
          }
        ],
        "responses": {
          "200": {
            "description": "Zeitreihen-Envelope (CanonicalRecord mit IndicatorSeriesPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ungueltige Jahresangabe in from oder to",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/municipal-finance": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Kommunale Finanzkennzahlen als Zeitreihe (Wegweiser, Tier A)",
        "x-summary-en": "Municipal finance indicators as a time series (Wegweiser, Tier A)",
        "description": "30 Indikatoren: Hebesaetze fuer Gewerbe- und Grundsteuer, Steuereinnahmekraft, kommunale Schulden, Investitionen, Personal- und Sozialausgaben je Einwohner. ABGRENZUNG: tax-rates (Regionalstatistik) bleibt die aktuellere Quelle fuer die reinen Hebesaetze; hier steht die Historie ab 2006. Jede Kennzahl traegt ihre ZEITREIHE mit latest_year und latest_value; Jahre ohne Wert fehlen in der Reihe (nie eine erfundene Null). Toggle aus -> 200 source_status=\"disabled\"; kein Snapshot oder leeres Jahresfenster -> 200 source_status=\"not_ingested\".",
        "x-description-en": "30 indicators: trade and property tax multipliers, tax revenue capacity, municipal debt, investment, staff and social spending per inhabitant. NOTE: tax-rates (Regionalstatistik) remains the more recent source for the multipliers themselves; this endpoint provides the history from 2006. Every metric carries its TIME SERIES plus latest_year and latest_value; years without a value are absent from the series (never an invented zero). Toggle off -> 200 source_status=\"disabled\"; no snapshot or an empty year window -> 200 source_status=\"not_ingested\".",
        "operationId": "getCityMunicipalFinance",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Erstes Jahr der Reihe (inklusive, 2006-2040). Ohne Angabe kommt die volle Reihe. Keine Zahl oder ausserhalb des Bestands -> 400.",
            "schema": {
              "type": "integer",
              "minimum": 2006,
              "maximum": 2040
            },
            "example": 2020
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Letztes Jahr der Reihe (inklusive, 2006-2040). Muss groesser oder gleich from sein, sonst 400.",
            "schema": {
              "type": "integer",
              "minimum": 2006,
              "maximum": 2040
            },
            "example": 2023
          }
        ],
        "responses": {
          "200": {
            "description": "Zeitreihen-Envelope (CanonicalRecord mit IndicatorSeriesPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ungueltige Jahresangabe in from oder to",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/labour-market": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Arbeitsmarkt und Pendler als Zeitreihe (Wegweiser, Tier A)",
        "x-summary-en": "Labour market and commuters as a time series (Wegweiser, Tier A)",
        "description": "40 Indikatoren: Arbeitslosenquoten (gesamt, Jugendliche, Langzeit, Auslaender), Beschaeftigungsquoten nach Alter und Geschlecht, geringfuegige Beschaeftigung, Hochqualifizierte, Ein- und Auspendler. ABGRENZUNG: unemployment (GENESIS) bleibt aktueller fuer die reine Arbeitslosenzahl, indicators (INKAR) nutzt eine andere Methodik. Jede Kennzahl traegt ihre ZEITREIHE mit latest_year und latest_value; Jahre ohne Wert fehlen in der Reihe (nie eine erfundene Null). Toggle aus -> 200 source_status=\"disabled\"; kein Snapshot oder leeres Jahresfenster -> 200 source_status=\"not_ingested\".",
        "x-description-en": "40 indicators: unemployment rates (total, youth, long-term, foreign nationals), employment rates by age and gender, marginal employment, highly qualified workers, in- and out-commuters. NOTE: unemployment (GENESIS) stays more recent for the plain unemployment figure, indicators (INKAR) uses a different methodology. Every metric carries its TIME SERIES plus latest_year and latest_value; years without a value are absent from the series (never an invented zero). Toggle off -> 200 source_status=\"disabled\"; no snapshot or an empty year window -> 200 source_status=\"not_ingested\".",
        "operationId": "getCityLabourMarket",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Erstes Jahr der Reihe (inklusive, 2006-2040). Ohne Angabe kommt die volle Reihe. Keine Zahl oder ausserhalb des Bestands -> 400.",
            "schema": {
              "type": "integer",
              "minimum": 2006,
              "maximum": 2040
            },
            "example": 2020
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Letztes Jahr der Reihe (inklusive, 2006-2040). Muss groesser oder gleich from sein, sonst 400.",
            "schema": {
              "type": "integer",
              "minimum": 2006,
              "maximum": 2040
            },
            "example": 2023
          }
        ],
        "responses": {
          "200": {
            "description": "Zeitreihen-Envelope (CanonicalRecord mit IndicatorSeriesPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ungueltige Jahresangabe in from oder to",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/integration": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Integrationskennzahlen als Zeitreihe (Wegweiser, Tier A)",
        "x-summary-en": "Integration indicators as a time series (Wegweiser, Tier A)",
        "description": "26 Indikatoren zur Lage von Menschen mit auslaendischer Staatsangehoerigkeit und Migrationshintergrund: Bevoelkerungsanteile, Beschaeftigung, Arbeitslosigkeit, Kinderbetreuung, Schulabschluesse, Einbuergerungen. Jede Kennzahl traegt ihre ZEITREIHE mit latest_year und latest_value; Jahre ohne Wert fehlen in der Reihe (nie eine erfundene Null). Toggle aus -> 200 source_status=\"disabled\"; kein Snapshot oder leeres Jahresfenster -> 200 source_status=\"not_ingested\".",
        "x-description-en": "26 indicators on the situation of people with foreign nationality or a migration background: population shares, employment, unemployment, childcare, school qualifications, naturalisations. Every metric carries its TIME SERIES plus latest_year and latest_value; years without a value are absent from the series (never an invented zero). Toggle off -> 200 source_status=\"disabled\"; no snapshot or an empty year window -> 200 source_status=\"not_ingested\".",
        "operationId": "getCityIntegration",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Erstes Jahr der Reihe (inklusive, 2006-2040). Ohne Angabe kommt die volle Reihe. Keine Zahl oder ausserhalb des Bestands -> 400.",
            "schema": {
              "type": "integer",
              "minimum": 2006,
              "maximum": 2040
            },
            "example": 2020
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Letztes Jahr der Reihe (inklusive, 2006-2040). Muss groesser oder gleich from sein, sonst 400.",
            "schema": {
              "type": "integer",
              "minimum": 2006,
              "maximum": 2040
            },
            "example": 2023
          }
        ],
        "responses": {
          "200": {
            "description": "Zeitreihen-Envelope (CanonicalRecord mit IndicatorSeriesPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ungueltige Jahresangabe in from oder to",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/childcare": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Kinderbetreuung als Zeitreihe (Wegweiser, Tier A)",
        "x-summary-en": "Childcare as a time series (Wegweiser, Tier A)",
        "description": "20 Indikatoren: Betreuungsquoten fuer unter Dreijaehrige, 3- bis 5-Jaehrige und Schulkinder, getrennt nach Tageseinrichtung und Tagespflege sowie nach Betreuungsumfang. Teilabdeckung: 83 der 84 Staedte (Reutlingen fehlt in der Quelle). Jede Kennzahl traegt ihre ZEITREIHE mit latest_year und latest_value; Jahre ohne Wert fehlen in der Reihe (nie eine erfundene Null). Toggle aus -> 200 source_status=\"disabled\"; kein Snapshot oder leeres Jahresfenster -> 200 source_status=\"not_ingested\".",
        "x-description-en": "20 indicators: care rates for under-threes, 3- to 5-year-olds and school children, split by day-care centre and childminder as well as by hours of care. Partial coverage: 83 of 84 cities (Reutlingen is missing at the source). Every metric carries its TIME SERIES plus latest_year and latest_value; years without a value are absent from the series (never an invented zero). Toggle off -> 200 source_status=\"disabled\"; no snapshot or an empty year window -> 200 source_status=\"not_ingested\".",
        "operationId": "getCityChildcare",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Erstes Jahr der Reihe (inklusive, 2006-2040). Ohne Angabe kommt die volle Reihe. Keine Zahl oder ausserhalb des Bestands -> 400.",
            "schema": {
              "type": "integer",
              "minimum": 2006,
              "maximum": 2040
            },
            "example": 2020
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Letztes Jahr der Reihe (inklusive, 2006-2040). Muss groesser oder gleich from sein, sonst 400.",
            "schema": {
              "type": "integer",
              "minimum": 2006,
              "maximum": 2040
            },
            "example": 2023
          }
        ],
        "responses": {
          "200": {
            "description": "Zeitreihen-Envelope (CanonicalRecord mit IndicatorSeriesPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ungueltige Jahresangabe in from oder to",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/education-stats": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Bildungsstatistik als Zeitreihe (Wegweiser, Tier A)",
        "x-summary-en": "Education statistics as a time series (Wegweiser, Tier A)",
        "description": "33 Indikatoren: Schulabgaenger nach Abschlussart, Uebergangsquoten, Auszubildende, Ausbildungsplaetze, Weiterbildungsbeteiligung. NICHT zu verwechseln mit education, das OSM-Schulstandorte als POIs fuehrt. Teilabdeckung: 70 Staedte, die 14 kreisangehoerigen fehlen (die Quelle fuehrt Bildungsdaten erst ab Kreisebene). Jede Kennzahl traegt ihre ZEITREIHE mit latest_year und latest_value; Jahre ohne Wert fehlen in der Reihe (nie eine erfundene Null). Toggle aus -> 200 source_status=\"disabled\"; kein Snapshot oder leeres Jahresfenster -> 200 source_status=\"not_ingested\".",
        "x-description-en": "33 indicators: school leavers by qualification, transition rates, apprentices, training places, participation in further education. NOT to be confused with education, which serves OSM school locations as POIs. Partial coverage: 70 cities; the 14 cities that are part of a district are missing (the source publishes education data from district level upwards). Every metric carries its TIME SERIES plus latest_year and latest_value; years without a value are absent from the series (never an invented zero). Toggle off -> 200 source_status=\"disabled\"; no snapshot or an empty year window -> 200 source_status=\"not_ingested\".",
        "operationId": "getCityEducationStats",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Erstes Jahr der Reihe (inklusive, 2006-2040). Ohne Angabe kommt die volle Reihe. Keine Zahl oder ausserhalb des Bestands -> 400.",
            "schema": {
              "type": "integer",
              "minimum": 2006,
              "maximum": 2040
            },
            "example": 2020
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Letztes Jahr der Reihe (inklusive, 2006-2040). Muss groesser oder gleich from sein, sonst 400.",
            "schema": {
              "type": "integer",
              "minimum": 2006,
              "maximum": 2040
            },
            "example": 2023
          }
        ],
        "responses": {
          "200": {
            "description": "Zeitreihen-Envelope (CanonicalRecord mit IndicatorSeriesPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ungueltige Jahresangabe in from oder to",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/social-situation": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Soziale Lage als Zeitreihe (Wegweiser, Tier A)",
        "x-summary-en": "Social situation as a time series (Wegweiser, Tier A)",
        "description": "17 Indikatoren: SGB-II-Quoten (gesamt, Kinder, Aeltere), Altersarmut, Grundsicherung, Wohngeld, Schuldnerquote, Einkommensverteilung. Jede Kennzahl traegt ihre ZEITREIHE mit latest_year und latest_value; Jahre ohne Wert fehlen in der Reihe (nie eine erfundene Null). Toggle aus -> 200 source_status=\"disabled\"; kein Snapshot oder leeres Jahresfenster -> 200 source_status=\"not_ingested\".",
        "x-description-en": "17 indicators: SGB II rates (total, children, elderly), old-age poverty, basic income support, housing benefit, over-indebtedness rate, income distribution. Every metric carries its TIME SERIES plus latest_year and latest_value; years without a value are absent from the series (never an invented zero). Toggle off -> 200 source_status=\"disabled\"; no snapshot or an empty year window -> 200 source_status=\"not_ingested\".",
        "operationId": "getCitySocialSituation",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Erstes Jahr der Reihe (inklusive, 2006-2040). Ohne Angabe kommt die volle Reihe. Keine Zahl oder ausserhalb des Bestands -> 400.",
            "schema": {
              "type": "integer",
              "minimum": 2006,
              "maximum": 2040
            },
            "example": 2020
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Letztes Jahr der Reihe (inklusive, 2006-2040). Muss groesser oder gleich from sein, sonst 400.",
            "schema": {
              "type": "integer",
              "minimum": 2006,
              "maximum": 2040
            },
            "example": 2023
          }
        ],
        "responses": {
          "200": {
            "description": "Zeitreihen-Envelope (CanonicalRecord mit IndicatorSeriesPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ungueltige Jahresangabe in from oder to",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/care": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Pflegekennzahlen als Zeitreihe (Wegweiser, Tier A)",
        "x-summary-en": "Long-term care indicators as a time series (Wegweiser, Tier A)",
        "description": "11 Indikatoren: Pflegebeduerftige je Altersgruppe, Pflegequote, Verteilung auf ambulante und stationaere Pflege sowie Pflegegeld, dazu die Pflegevorausberechnung bis 2030. Teilabdeckung: 73 Staedte. Jede Kennzahl traegt ihre ZEITREIHE mit latest_year und latest_value; Jahre ohne Wert fehlen in der Reihe (nie eine erfundene Null). Toggle aus -> 200 source_status=\"disabled\"; kein Snapshot oder leeres Jahresfenster -> 200 source_status=\"not_ingested\".",
        "x-description-en": "11 indicators: people in need of care by age group, care rate, split across outpatient care, inpatient care and care allowance, plus the care forecast to 2030. Partial coverage: 73 cities. Every metric carries its TIME SERIES plus latest_year and latest_value; years without a value are absent from the series (never an invented zero). Toggle off -> 200 source_status=\"disabled\"; no snapshot or an empty year window -> 200 source_status=\"not_ingested\".",
        "operationId": "getCityCare",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Erstes Jahr der Reihe (inklusive, 2006-2040). Ohne Angabe kommt die volle Reihe. Keine Zahl oder ausserhalb des Bestands -> 400.",
            "schema": {
              "type": "integer",
              "minimum": 2006,
              "maximum": 2040
            },
            "example": 2020
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Letztes Jahr der Reihe (inklusive, 2006-2040). Muss groesser oder gleich from sein, sonst 400.",
            "schema": {
              "type": "integer",
              "minimum": 2006,
              "maximum": 2040
            },
            "example": 2023
          }
        ],
        "responses": {
          "200": {
            "description": "Zeitreihen-Envelope (CanonicalRecord mit IndicatorSeriesPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ungueltige Jahresangabe in from oder to",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/land-values": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Amtliche Bodenrichtwerte je Stadt, aggregiert (BORIS, Tier A)",
        "x-summary-en": "Official land values per city, aggregated (BORIS, Tier A)",
        "description": "Amtliche Bodenrichtwerte (BORIS, Bodenrichtwert-Informationssystem der Gutachterausschüsse) je Stadt zu einer Bauland-Kennzahl aggregiert (Bauland = Wohnen/Misch/Gewerbe, ohne Wald/Wasser/Landwirtschaft): brw_median_eur_m2, brw_min_eur_m2, brw_max_eur_m2, zone_count, der Bewertungsstichtag (kanonisch reference_date; das gleichwertige stichtag ist abgekündigt und wird frühestens 30 Tage nach Ankündigung entfernt) und der bbox_radius_deg (Umkreis um das Stadtzentrum). BORIS ist pro Bundesland föderiert (je Land ein WFS). Toggle aus -> 200 source_status=\"disabled\"; Bundesland ohne BORIS-WFS -> 200 source_status=\"not_covered\" (mit covered_cities); abgedeckt, aber kein Snapshot -> 200 source_status=\"not_ingested\".",
        "x-description-en": "Official land values (BORIS, the surveyor committees' land-value information system) per city, aggregated into a building-land metric (building land = residential/mixed/commercial, excluding forest/water/farmland): brw_median_eur_m2, brw_min_eur_m2, brw_max_eur_m2, zone_count, the valuation reference date (canonically reference_date; the equivalent stichtag is deprecated and will be removed no earlier than 30 days after the announcement) and bbox_radius_deg (radius around the city centre). BORIS is federated per federal state (one WFS per state). Toggle off -> 200 source_status=\"disabled\"; state without a BORIS WFS -> 200 source_status=\"not_covered\" (with covered_cities); covered but no snapshot -> 200 source_status=\"not_ingested\".",
        "operationId": "getCityLandValues",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bodenrichtwerte-Envelope (CanonicalRecord mit LandValuesPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/tax-rates": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Realsteuer-Hebesätze je Gemeinde (Regionalstatistik, Tier A)",
        "x-summary-en": "Local business/property tax rates per municipality (Regionalstatistik, Tier A)",
        "description": "Amtliche Realsteuer-Hebesätze GEMEINDE-genau (Regionalstatistik der Statistischen Ämter, Tabelle 71231): trade_tax_rate (Gewerbesteuer), property_tax_a/b/c (Grundsteuer, alle in %, ein nicht festgesetzter Satz ist null) sowie reference_date (Stand 31.12., neuester Jahrgang). Die gleichwertigen deutschen Felder gewerbesteuer_hebesatz, grundsteuer_a/b/c und stichtag sind abgekündigt und werden frühestens 30 Tage nach Ankündigung entfernt. Standort-/immobilienrelevant. Toggle aus oder keine GENESIS-Credentials -> 200 source_status=\"disabled\"; Batch nicht gelaufen -> 200 source_status=\"not_ingested\".",
        "x-description-en": "Official local real-property tax multipliers per MUNICIPALITY (Regionalstatistik of the German statistical offices, table 71231): trade_tax_rate, property_tax_a/b/c (all in %, an unset rate is null) plus reference_date (as of Dec 31, latest year). The equivalent German fields gewerbesteuer_hebesatz, grundsteuer_a/b/c and stichtag are deprecated and will be removed no earlier than 30 days after the announcement. Toggle off or no GENESIS credentials -> 200 source_status=\"disabled\"; batch not run -> 200 source_status=\"not_ingested\".",
        "operationId": "getCityTaxRates",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Hebesatz-Envelope (CanonicalRecord mit TaxRatesPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/business-registrations": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Gewerbean-/-abmeldungen je Kreis (Regionalstatistik, Tier A)",
        "x-summary-en": "Business registrations/deregistrations per district (Regionalstatistik, Tier A)",
        "description": "Gewerbean-/-abmeldungen KREIS-genau (Gewerbeanzeigenstatistik der Statistischen Ämter, Tabelle 52311, Jahressumme, ohne Automatenaufsteller): registrations, deregistrations, balance (registrations minus deregistrations; positiv = Netto-Gründungsplus) und das Berichtsjahr (year). Die gleichwertigen deutschen Felder anmeldungen, abmeldungen, saldo und jahr sind abgekündigt und werden frühestens 30 Tage nach Ankündigung entfernt. Gründungsdynamik je Stadt. Toggle aus oder keine GENESIS-Credentials -> 200 source_status=\"disabled\"; Batch nicht gelaufen -> 200 source_status=\"not_ingested\".",
        "x-description-en": "Business registrations and deregistrations per DISTRICT (German business notification statistics, table 52311, annual total, excluding vending machines): registrations, deregistrations, balance (net, positive = founding surplus) and the reporting year (year). The equivalent German fields anmeldungen, abmeldungen, saldo and jahr are deprecated and will be removed no earlier than 30 days after the announcement. Toggle off or no GENESIS credentials -> 200 source_status=\"disabled\"; batch not run -> 200 source_status=\"not_ingested\".",
        "operationId": "getCityBusinessRegistrations",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Gewerbeanzeigen-Envelope (CanonicalRecord mit BusinessRegistrationsPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/insolvencies": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Beantragte Insolvenzen je Kreis (Regionalstatistik, Tier A)",
        "x-summary-en": "Filed insolvencies per district (Regionalstatistik, Tier A)",
        "description": "Beantragte Insolvenzen KREIS-genau (Insolvenzstatistik der Statistischen Ämter, Tabelle 52411, Jahressumme): corporate_insolvencies (beantragte Unternehmensinsolvenzen, Tabelle 52411-02) und other_debtor_insolvencies (beantragte Insolvenzen übriger Schuldner, Tabelle 52411-03; umfasst Verbraucher, ehemalige Selbständige und sonstige natürliche Personen, NICHT nur Verbraucher) sowie das Berichtsjahr (year, neuester Jahrgang mit beiden Werten). Die gleichwertigen deutschen Felder unternehmensinsolvenzen, uebrige_schuldner_insolvenzen und jahr sind abgekündigt und werden frühestens 30 Tage nach Ankündigung entfernt. Toggle aus oder keine GENESIS-Credentials -> 200 source_status=\"disabled\"; Batch nicht gelaufen -> 200 source_status=\"not_ingested\".",
        "x-description-en": "Filed insolvencies per DISTRICT (German insolvency statistics, table 52411, annual total): corporate_insolvencies (filed corporate insolvencies, table 52411-02) and other_debtor_insolvencies (filed insolvencies of other debtors, table 52411-03; covers consumers, former self-employed and other natural persons, NOT only consumers) plus the reporting year (year, latest year with both values). The equivalent German fields unternehmensinsolvenzen, uebrige_schuldner_insolvenzen and jahr are deprecated and will be removed no earlier than 30 days after the announcement. Toggle off or no GENESIS credentials -> 200 source_status=\"disabled\"; batch not run -> 200 source_status=\"not_ingested\".",
        "operationId": "getCityInsolvencies",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Insolvenzen-Envelope (CanonicalRecord mit InsolvenciesPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/solar": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Solar-Einstrahlung + PV-Ertrag je Stadt (PVGIS, Tier A)",
        "x-summary-en": "Solar irradiation + PV yield per city (PVGIS, Tier A)",
        "description": "Solar-Potenzial je Stadt aus der keylosen PVGIS-Rechen-API (EU JRC, PVcalc). PVGIS rechnet jede EU-Koordinate, daher sind alle Städte abgedeckt. Klimatologisches Mehrjahresmittel am Stadtzentrum, normiert auf 1 kWp bei optimalem Neigungswinkel: annual_yield_kwh_kwp (Jahresertrag kWh/kWp), annual_irradiation_kwh_m2 (Globalstrahlung kWh/m²), optimal_slope_deg/optimal_azimuth_deg und 12 Monatswerte (monthly). system_loss_pct ist der konfigurierte (gewollte) Systemverlust aus Verkabelung/Wechselrichter/Verschmutzung (an PVGIS übergebener loss-Parameter, i.d.R. 14 %), NICHT die Gesamt-Performance-Differenz; diese steht separat als total_performance_delta_pct (PVGIS l_total, inkl. Temperatur-/Einstrahlungs-/Winkel-Effekten, negativ = Gesamtminderung). Kein Messzeitpunkt, daher observed_at=null; der Bezugszeitraum steht als period_start/period_end. Tier A (EU-Reuse-Policy). Toggle aus -> 200 source_status=\"disabled\"; toter Upstream ohne Cache -> 503 mit Hint.",
        "x-description-en": "Solar potential per city from the keyless PVGIS computation API (EU JRC, PVcalc). PVGIS computes any EU coordinate, so all cities are covered. A multi-year climatological average at the city centre, normalized to a 1 kWp system at the optimal tilt: annual_yield_kwh_kwp (annual yield kWh/kWp), annual_irradiation_kwh_m2 (global irradiation kWh/m²), optimal_slope_deg/optimal_azimuth_deg and 12 monthly values (monthly). system_loss_pct is the configured (intended) system loss from cabling/inverter/soiling (the loss parameter passed to PVGIS, typically 14 %), NOT the overall performance delta; that is carried separately as total_performance_delta_pct (PVGIS l_total, incl. temperature/irradiance/ angle effects, negative = overall reduction). No observation timestamp, so observed_at=null; the reference period is carried as period_start/period_end. Tier A (EU reuse policy). Toggle off -> 200 source_status=\"disabled\"; dead upstream with no cache -> 503 with a hint.",
        "operationId": "getCitySolar",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Solar-Envelope (CanonicalRecord mit SolarPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/parking": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Parkhaus-Belegung/-Katalog je Stadt (EIN Endpunkt mit Quellen-Fallback)",
        "x-summary-en": "Parking occupancy/catalogue per city (one endpoint, source fallback)",
        "description": "Parkhaus-Daten je Stadt aus EINEM Endpunkt mit Quellen-Fallback (Dedup): bevorzugt ParkenDD-Live-Belegung (frei/gesamt je Parkhaus, 13 Städte mit verifiziert offener Lizenz, keylos), für München der statische Open-Data-Standortkatalog (DL-DE/BY 2.0, ohne Live-Belegung), für Frankfurt am Main, Wuppertal und Magdeburg die Mobilithek-Quellen der Städte (DATEX II, Live-Belegung + Stammdaten, DL-DE/BY bzw. DL-DE/Zero). Lizenz/Tier kommen pro Datensatz aus der am Ursprung verifizierten Quelle (alle Tier A: z.B. Köln/Dortmund DL-DE/Zero 2.0, Hamburg DL-DE/BY 2.0, Aachen/Ulm CC0, Karlsruhe/Heidelberg CC BY 4.0). Löst den deprecateten Pfad /api/v1/live/dortmund/parking ab. Toggle aus -> 200 source_status=\"disabled\"; nicht-abgedeckte Stadt -> 200 source_status=\"not_covered\" (mit covered_cities); abgedeckte Stadt ohne Parkhaus -> 200 source_status=\"no_data\". Staleness-Guard: ist der ParkenDD-Datenstand (last_updated) aelter als 48 Stunden (eingefrorener Upstream, z.B. Koeln seit 2021), antwortet die Stadt ehrlich mit source_status=\"no_data\" statt einer veralteten \"Live\"-Belegung; liefert der Ursprung wieder, kippt sie automatisch auf ok zurueck.",
        "x-description-en": "Per-city parking data from ONE endpoint with a source fallback (dedup): prefers ParkenDD live occupancy (free/total per facility, 13 cities with a verified open license, key-less), and for Munich the static open-data location catalogue (DL-DE/BY 2.0, no live occupancy), and for Frankfurt, Wuppertal and Magdeburg the cities own Mobilithek sources (DATEX II, live occupancy + master data, DL-DE/BY or DL-DE/Zero). License/tier are per record from the source verified at origin (all Tier A: e.g. Cologne/Dortmund DL-DE/Zero 2.0, Hamburg DL-DE/BY 2.0, Aachen/Ulm CC0, Karlsruhe/Heidelberg CC BY 4.0). Supersedes the deprecated path /api/v1/live/dortmund/parking. Toggle off -> 200 source_status=\"disabled\"; city not covered -> 200 source_status=\"not_covered\" (with covered_cities); covered city without a parking facility -> 200 source_status=\"no_data\". Staleness guard: if the ParkenDD data timestamp (last_updated) is older than 48 hours (frozen upstream, e.g. Cologne since 2021), the city honestly answers with source_status=\"no_data\" instead of an outdated \"live\" occupancy; once the origin delivers again, it automatically flips back to ok.",
        "operationId": "getCityParking",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Parking-Envelope (CanonicalRecord mit ParkingPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/bike-counts": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Radzählstellen je Stadt (kommunale Open Data, Tier A)",
        "x-summary-en": "Bicycle counting stations per city (municipal open data, Tier A)",
        "description": "Radzählstellen (Dauerzählstellen) je Stadt aus kommunalen Open-Data-Quellen, je Ursprung lizenzverifiziert. Liefert je Zählstelle (counts) Name, Koordinaten, Zählwert (value), Granularität (granularity, z.B. \"day\") und ggf. die Werte je Richtung (directions). Eco-Counter/Eco-Visio ist bewusst NICHT eingebunden (Lizenz ungeklärt). Aktuell: München (Mobilitätsreferat, DL-DE/BY 2.0, Tagessummen der 6 Dauerzählstellen). Teilabgedeckt: Toggle aus -> 200 source_status=\"disabled\"; nicht-abgedeckte Stadt -> 200 source_status=\"not_covered\" (mit covered_cities); abgedeckte Stadt ohne Zählstelle -> 200 source_status=\"no_data\". Granularität je Quelle (siehe counts[].granularity): München Tag, Leipzig/Hamburg/Berlin Stunde, Stuttgart/Köln/Essen/Düsseldorf Jahr. Lizenzen je Ursprung (München/Leipzig/Hamburg/Essen/Düsseldorf DL-DE/BY 2.0, Berlin/Köln DL-DE/Zero 2.0, Stuttgart CC BY 4.0).",
        "x-description-en": "Bicycle counting stations (permanent counters) per city from municipal open-data sources, license-verified at origin. Each station (counts) carries name, coordinates, value, granularity (e.g. \"day\") and optionally per-direction values (directions). Eco-Counter/Eco-Visio is deliberately NOT included (license unclear). Currently: Munich (Mobilitätsreferat, DL-DE/BY 2.0, daily totals of the 6 permanent counters). Partial coverage: toggle off -> 200 source_status=\"disabled\"; city not covered -> 200 source_status=\"not_covered\" (with covered_cities); covered city without a station -> 200 source_status=\"no_data\".",
        "operationId": "getCityBikeCounts",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bike-Counts-Envelope (CanonicalRecord mit CountStationPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/office-wait-times": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Behörden-Wartezeiten je Stadt (live, keylos, Tier A)",
        "x-summary-en": "Government office wait times per city (live, keyless, Tier A)",
        "description": "Live-Wartezeiten der Bürgerämter/Kundenzentren + Kfz-Zulassungsstelle je Stadt aus dem offenen Feed der Stadt. Liefert je Standort (offices) name, wait_minutes (Wartezeit in Minuten, null bei fehlendem/nicht-numerischem Wert), is_open (bool), status_text (Klartext), detail_url (Detailseite) und observed_at (ISO-UTC). Reine Live-Daten (keine Speicherung). Aktuell: Köln (waiting-od.php, DL-DE/Zero 2.0, minütlich während der Öffnungszeiten). Teilabgedeckt: Toggle aus -> 200 source_status=\"disabled\"; nicht-abgedeckte Stadt -> 200 source_status=\"not_covered\" (mit covered_cities); abgedeckte Stadt ohne Standort -> 200 source_status=\"no_data\"; toter Upstream ohne Cache -> 503 mit Hint. Kaputte Upstream-Felder führen nie zu 500 (Zero-Trust).",
        "x-description-en": "Live wait times of citizen offices/service centres and the vehicle registration office per city from the city's open feed. Each office carries name, wait_minutes (null when missing/non-numeric), is_open, status_text, detail_url and observed_at (ISO-UTC). Live-only (not persisted). Currently: Cologne (waiting-od.php, DL-DE/Zero 2.0). Partial coverage: toggle off -> 200 source_status=\"disabled\"; city not covered -> 200 source_status=\"not_covered\" (with covered_cities); covered city without an office -> 200 source_status=\"no_data\". Broken upstream fields never cause 500.",
        "operationId": "getCityOfficeWaitTimes",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Office-Wait-Times-Envelope (CanonicalRecord mit OfficeWaitTimesPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/parking-onstreet": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Strassenparkraum je Stadt (Tier A, keylos)",
        "x-summary-en": "On-street parking per city (Tier A, keyless)",
        "description": "Bewirtschafteter Strassenparkraum je Stadt aus den offenen Fachdaten der Stadt. Liefert die Gesamtsummen (segment_count = Strassenabschnitte mit eigener Parkregelung, total_spaces = Stellplaetze, zone_count, accessible_bay_count/accessible_spaces, loading_zone_count/loading_spaces) plus drei Aggregate: by_regulation (je Regelungsgruppe wie Bewohnerparken oder Mischparken), by_street (je Strasse, alphabetisch) und zones (je Parkraummanagementgebiet mit measure, status, enforcement, opened). Dazu accessible_bays (Behindertenparkplaetze) und loading_zones (Laden, Liefern, Leisten), je Eintrag mit lat/lon. Die Rohsegmente werden bewusst nicht ausgeliefert. Stammdaten, KEINE Belegung. Aktuell: Muenchen (WFS des Mobilitaetsreferats, DL-DE/BY 2.0). Teilabgedeckt: Toggle aus -> 200 source_status=\"disabled\"; nicht-abgedeckte Stadt -> 200 source_status=\"not_covered\" (mit covered_cities); abgedeckte Stadt ohne Daten -> 200 source_status=\"no_data\"; toter Upstream ohne Cache -> 503 mit Hint. Kaputte Upstream-Felder fuehren nie zu 500 (Zero-Trust).",
        "x-description-en": "Managed on-street parking per city from the city's open thematic data. Returns totals (segment_count = street sections with their own parking rule, total_spaces, zone_count, accessible_bay_count/accessible_spaces, loading_zone_count/loading_spaces) plus three aggregates: by_regulation (per rule group), by_street (alphabetical) and zones (per parking management area with measure, status, enforcement, opened). Plus accessible_bays and loading_zones, each with lat/lon. Raw segments are deliberately not served. Master data, NO occupancy. Currently: Munich (WFS of the mobility department, DL-DE/BY 2.0). Partial coverage: toggle off -> 200 source_status=\"disabled\"; city not covered -> 200 source_status=\"not_covered\" (with covered_cities); covered city without data -> 200 source_status=\"no_data\". Broken upstream fields never cause 500.",
        "operationId": "getCityParkingOnstreet",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Parking-Onstreet-Envelope (CanonicalRecord mit ParkingOnStreetPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/park-and-ride": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "P+R- und B+R-Anlagen je Stadt (Tier A, keylos)",
        "x-summary-en": "Park & ride facilities per city (Tier A, keyless)",
        "description": "Park-and-Ride- und Bike-and-Ride-Anlagen je Stadt. car_facilities traegt je Anlage name, address, lat/lon, die Stellplatzzahlen (spaces_total plus spaces_accessible, spaces_women, spaces_family, spaces_electric, spaces_motorcycle), structure_type, entrance_height_m, barrier_operation, max_duration, die Preise (price_single_eur, price_ten_trip_eur, price_month_eur, price_year_eur, price_level), transit_lines und occupancy_forecast. occupancy_forecast ist eine PROGNOSE aus historischen Erfahrungswerten (Ampelstufe green/yellow/red je Zeitscheibe, getrennt fuer weekday/saturday/sunday), KEINE Echtzeit-Belegung. bike_facilities traegt spaces, structure_type, at_park_and_ride und transit_lines. Aktuell: Muenchen (P+R Park & Ride GmbH Muenchen, DL-DE/BY 2.0). Teilabgedeckt: Toggle aus -> 200 source_status=\"disabled\"; nicht-abgedeckte Stadt -> 200 source_status=\"not_covered\" (mit covered_cities); abgedeckte Stadt ohne Daten -> 200 source_status=\"no_data\"; toter Upstream ohne Cache -> 503 mit Hint. Kaputte Upstream-Felder fuehren nie zu 500 (Zero-Trust).",
        "x-description-en": "Park and ride plus bike and ride facilities per city. car_facilities carries name, address, lat/lon, the space counts (spaces_total plus spaces_accessible, spaces_women, spaces_family, spaces_electric, spaces_motorcycle), structure_type, entrance_height_m, barrier_operation, max_duration, the prices (price_single_eur, price_ten_trip_eur, price_month_eur, price_year_eur, price_level), transit_lines and occupancy_forecast. occupancy_forecast is a FORECAST from historical experience (green/yellow/red per time slot, separately for weekday/saturday/sunday), NOT live occupancy. bike_facilities carries spaces, structure_type, at_park_and_ride and transit_lines. Currently: Munich (P+R Park & Ride GmbH Muenchen, DL-DE/BY 2.0). Partial coverage: toggle off -> 200 source_status=\"disabled\"; city not covered -> 200 source_status=\"not_covered\" (with covered_cities); covered city without data -> 200 source_status=\"no_data\". Broken upstream fields never cause 500.",
        "operationId": "getCityParkAndRide",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Park-and-Ride-Envelope (CanonicalRecord mit ParkAndRidePayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/mobility-points": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Mobilitaetspunkte und Carsharing-Parkflaechen je Stadt (Tier A, keylos)",
        "x-summary-en": "Mobility points and car-sharing parking areas per city (Tier A, keyless)",
        "description": "Mobilitaetspunkte je Stadt: eine Informationsstele buendelt Carsharing-Stellplaetze (carsharing_spaces), Taxi-Stellplaetze, Ladepunkte (charging_points_ac/charging_points_dc), Abstellflaechen fuer geteilte Mikromobilitaet (has_scooter_area, has_bikeshare_area, has_cargo_bike_area, has_moped_area), Radservice (has_bike_service_station, has_bike_pump) und die OePNV-Anbindung (near_bus, near_tram, near_subway, near_suburban_rail). Dazu carsharing_areas mit kind=general (alle in der Stadt registrierten Carsharing-Fahrzeuge) bzw. kind=station_based (nur die dem Anbieter zugewiesenen), je mit provider, district, in_service_since und Koordinate. Aktuell: Muenchen (WFS des Mobilitaetsreferats, DL-DE/BY 2.0). Teilabgedeckt: Toggle aus -> 200 source_status=\"disabled\"; nicht-abgedeckte Stadt -> 200 source_status=\"not_covered\" (mit covered_cities); abgedeckte Stadt ohne Daten -> 200 source_status=\"no_data\"; toter Upstream ohne Cache -> 503 mit Hint. Kaputte Upstream-Felder fuehren nie zu 500 (Zero-Trust).",
        "x-description-en": "Mobility points per city: one information pillar bundles car-sharing bays (carsharing_spaces), taxi bays, charging points (charging_points_ac/charging_points_dc), parking areas for shared micromobility (has_scooter_area, has_bikeshare_area, has_cargo_bike_area, has_moped_area), bike service (has_bike_service_station, has_bike_pump) and public transport access (near_bus, near_tram, near_subway, near_suburban_rail). Plus carsharing_areas with kind=general (any car-sharing vehicle registered in the city) or kind=station_based (only the provider's assigned vehicles), each with provider, district, in_service_since and coordinate. Currently: Munich (WFS of the mobility department, DL-DE/BY 2.0). Partial coverage: toggle off -> 200 source_status=\"disabled\"; city not covered -> 200 source_status=\"not_covered\" (with covered_cities); covered city without data -> 200 source_status=\"no_data\". Broken upstream fields never cause 500.",
        "operationId": "getCityMobilityPoints",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Mobility-Points-Envelope (CanonicalRecord mit MobilityPointPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/bike-parking": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Radabstellanlagen je Stadt (Tier A, keylos)",
        "x-summary-en": "Bike parking facilities per city (Tier A, keyless)",
        "description": "Radparkraum je Stadt: Bestand an Fahrradabstellanlagen mit Stellplatzsumme (facility_count, spaces_total), Merkmalen (covered_facilities, double_deck_facilities, lit_facilities, time_limited_facilities) und Bike-and-Ride-Anlagen samt Plaetzen (bike_and_ride_facilities, bike_and_ride_spaces). by_type aggregiert je Bauform (Anlehnbuegel, Rahmenhalter, Vorderradhalter, Doppelstockabstellanlage, Fahrradstaender), cargo_bike fuehrt die Lastenradanlagen getrennt, largest_facilities die zwanzig groessten Standorte mit Ort, Plaetzen, Bauform und Merkmalen (time_limit als Freitext der Quelle). GEZAEHLT WIRD NUR DER BESTAND: geplante, abgebaute und ausser Betrieb genommene Anlagen fuehrt die Quelle im selben Layer und stehen als planned_facilities/planned_spaces, removed_facilities und out_of_service_facilities daneben, gehen aber nicht in spaces_total ein. Aktuell: Muenchen (zwei WFS-Layer des Mobilitaetsreferats, DL-DE/BY 2.0). Teilabgedeckt: Toggle aus -> 200 source_status=\"disabled\"; nicht-abgedeckte Stadt -> 200 source_status=\"not_covered\" (mit covered_cities); abgedeckte Stadt ohne Daten -> 200 source_status=\"no_data\"; toter Upstream ohne Cache -> 503 mit Hint. Kaputte Upstream-Felder fuehren nie zu 500 (Zero-Trust).",
        "x-description-en": "Bike parking per city: the in-service stock of bicycle parking facilities with total spaces (facility_count, spaces_total), features (covered_facilities, double_deck_facilities, lit_facilities, time_limited_facilities) and bike-and-ride facilities including spaces (bike_and_ride_facilities, bike_and_ride_spaces). by_type aggregates per construction type, cargo_bike carries the cargo bike facilities separately, largest_facilities the twenty biggest locations with place, spaces, type and features (time_limit as free text from the source). ONLY THE IN-SERVICE STOCK IS COUNTED: planned, removed and out-of-service facilities live in the same source layer and are reported separately as planned_facilities/planned_spaces, removed_facilities and out_of_service_facilities, but never enter spaces_total. Currently: Munich (two WFS layers of the mobility department, DL-DE/BY 2.0). Partial coverage: toggle off -> 200 source_status=\"disabled\"; city not covered -> 200 source_status=\"not_covered\" (with covered_cities); covered city without data -> 200 source_status=\"no_data\". Broken upstream fields never cause 500.",
        "operationId": "getCityBikeParking",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bike-Parking-Envelope (CanonicalRecord mit BikeParkingPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/solar-roofs": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Dach-Solarkataster je Stadt, föderiert (Tier A)",
        "x-summary-en": "Rooftop solar cadastre per city, federated (Tier A)",
        "description": "Dach-Solarkataster je Stadt aus den amtlichen Aggregaten der Länder (NRW: Solarkataster NRW, MaStR/LANUK/Geobasis NRW, DL-DE/Zero 2.0; Bayern: Energie-Atlas Bayern, CC BY 4.0; Berlin: Umweltatlas/SenMVKU, DL-DE/Zero 2.0; Hamburg: LGV-Solarpotenzialanalyse, DL-DE/BY 2.0). Liefert das gesamte installierbare Dach-PV-Potenzial (potential_kwp, potential_yield_mwh), den bereits installierten Bestand (installed_kwp, installed_yield_mwh), den Ausschöpfungsgrad (exploitation_pct) und die Aufschlüsselung des Potenzials je Gebäudekategorie (potential_by_category). Der Umfang variiert je Quelle (z.B. Hamburg nur Potenzial, keine Kategorien). Anders als /solar (PVGIS-Einstrahlung je kWp) trägt diese Route die Mengen je Stadt. Teilabgedeckt, föderiert je Bundesland: Toggle aus -> 200 source_status=\"disabled\"; Stadt außerhalb der abgedeckten Länder -> 200 source_status=\"not_covered\" (mit covered_cities); abgedeckte Stadt ohne Seed-Eintrag -> 200 source_status=\"no_data\".",
        "x-description-en": "Rooftop solar cadastre per city from the official state aggregates (NRW: Solarkataster NRW, MaStR/LANUK/Geobasis NRW, DL-DE/Zero 2.0; Bavaria: Energie-Atlas Bayern, CC BY 4.0; Berlin: Umweltatlas/SenMVKU, DL-DE/Zero 2.0; Hamburg: LGV solar potential analysis, DL-DE/BY 2.0). Returns the total installable rooftop PV potential (potential_kwp, potential_yield_mwh), the already installed stock (installed_kwp, installed_yield_mwh), the exploitation ratio (exploitation_pct) and the potential broken down per building category (potential_by_category). Coverage varies by source (e.g. Hamburg potential only, no categories). Unlike /solar (PVGIS irradiation per kWp) this route carries the per-city quantities. Partial coverage, federated per state: toggle off -> 200 source_status=\"disabled\"; city outside covered states -> 200 source_status=\"not_covered\" (with covered_cities); covered city without a seed entry -> 200 source_status=\"no_data\".",
        "operationId": "getCitySolarRoofs",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Solar-Roofs-Envelope (CanonicalRecord mit SolarRoofsPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/district-heating": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Fernwärme- & Wärmenetz-Versorgung je Stadt, föderiert (Tier A)",
        "x-summary-en": "District-heating / heat-network supply per city, federated (Tier A)",
        "description": "Fernwärme-/Wärmenetz-Versorgung je Stadt aus den amtlichen Geodaten der kommunalen Wärmeplanung, föderiert je Stadt-WFS (Berlin: Energienetze, Netzgebiet Fernwärme einschl. 250 m Puffer, DL-DE/Zero 2.0; Hamburg: Gebiete mit Wärmenetz der kommunalen Wärmeplanung, DL-DE/BY 2.0). Liefert die Netzbetreiber (operators, operator_count), die Zahl der Versorgungs-/ Netzflächen (network_area_count) und je nach Quelle die versorgte Fläche (supplied_area_km2, Berlin) bzw. die Hausanschlüsse und Trassenlänge (house_connections, network_length_km, Hamburg) sowie je Betreiber ein Detail-Aggregat (networks). Read-only aus dem Batch-Store (kein WFS im Request-Pfad). Teilabgedeckt, föderiert je Stadt: Toggle aus -> 200 source_status=\"disabled\"; Stadt außerhalb -> 200 source_status=\"not_covered\" (mit covered_cities); abgedeckte Stadt ohne Snapshot -> 200 source_status=\"not_ingested\".",
        "x-description-en": "District-heating / heat-network supply per city from the official municipal heat-planning geodata, federated per-city WFS (Berlin: Energienetze, heat-network supply area incl. 250 m buffer, DL-DE/Zero 2.0; Hamburg: areas with a heat network from municipal heat planning, DL-DE/BY 2.0). Returns the network operators (operators, operator_count), the number of supply/network areas (network_area_count) and, depending on the source, the supplied area (supplied_area_km2, Berlin) or the house connections and trench length (house_connections, network_length_km, Hamburg), plus a per-operator detail aggregate (networks). Read-only from the batch store (no WFS in the request path). Partial coverage, federated per city: toggle off -> 200 source_status=\"disabled\"; city outside -> 200 source_status=\"not_covered\" (with covered_cities); covered city without a snapshot -> 200 source_status=\"not_ingested\".",
        "operationId": "getCityDistrictHeating",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "District-Heating-Envelope (CanonicalRecord mit DistrictHeatingPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/sharing": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Bike-/Scooter-Sharing je Stadt, aggregiert (GBFS, Tier A)",
        "x-summary-en": "Bike/scooter sharing per city, aggregated (GBFS, Tier A)",
        "description": "Live-Bike-/Scooter-Sharing je Stadt, aus offenen GBFS-Feeds der kuratierten Tier-A-Anbieter (Nextbike, CC0; für Frankfurt am Main DB Call a Bike über MobiData BW, DL-DE/BY-2.0) im Stadtgebiet zu einer Kennzahl aggregiert: vehicles_available (frei abgestellt + an Stationen), station_count und je Anbieter eine fail-closed verifizierte Tier-A-license_id. Toggle aus -> 200 source_status=\"disabled\"; Stadt ohne kuratiertes GBFS-System -> 200 source_status=\"not_covered\" (mit covered_cities); kein akzeptierter Tier-A-Anbieter -> 200 source_status=\"no_data\".",
        "x-description-en": "Live bike/scooter sharing per city, aggregated from open GBFS feeds of the curated Tier-A providers (Nextbike, CC0; for Frankfurt am Main DB Call a Bike via MobiData BW, DL-DE/BY-2.0) within the city area: vehicles_available (free-floating + docked), station_count and a fail-closed verified Tier-A license_id per provider. Toggle off -> 200 source_status=\"disabled\"; city without a curated GBFS system -> 200 source_status=\"not_covered\" (with covered_cities); no accepted Tier-A provider -> 200 source_status=\"no_data\".",
        "operationId": "getCitySharing",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sharing-Envelope (CanonicalRecord mit SharingPayload) oder null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/health": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Krankenhaus-Stammdaten je Stadt (Destatis-Verzeichnis, Tier A)",
        "x-summary-en": "Hospital master data per city (Destatis directory, Tier A)",
        "description": "Liefert die Krankenhaus-Stammdaten einer Stadt aus dem Destatis-Krankenhausverzeichnis (über GENESIS) im kanonischen Envelope. Die Attribution trägt den exakten Destatis-Wortlaut (nicht pauschal DL-DE/BY). Tier A. Deaktiviert -> 200 source_status=\"disabled\". Toter Upstream ohne Cache -> 503 mit Hint.",
        "x-description-en": "Returns a city's hospital master data from the Destatis hospital directory (via GENESIS) in the canonical envelope. The attribution carries the exact Destatis wording (not a generic DL-DE/BY). Tier A. Disabled -> 200 source_status=\"disabled\". Dead upstream with no cache -> 503 with a hint.",
        "operationId": "getCityHealth",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Krankenhaus-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/road-events": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Innerstädtische Baustellen und Sperrungen je Stadt (Tier A)",
        "x-summary-en": "Inner-city roadworks and closures per city (Tier A)",
        "description": "Liefert innerstädtische Baustellen und Sperrungen einer Stadt aus den schlüssellosen Pro-Stadt-Quellen (Berlin VIZ, Hamburg, Köln, München, MobiData BW, Dortmund, SPERRINFOSYS Sachsen) im kanonischen Envelope. Die Events tragen kanonische englische Feldnamen (z.B. description, event_type, start, end, street); die gleichwertigen deutschen Felder der Stadtquellen (z.B. beschreibung, art, von, bis, strasse) sind abgekündigt und werden frühestens 30 Tage nach Ankündigung entfernt. Teilabdeckung (kuratierte Connectoren): berlin, hamburg, koeln, muenchen, stuttgart, dortmund, bremen, dresden, leipzig liefern Daten. Eine Stadt ohne Connector antwortet ehrlich mit 200 source_status=\"not_covered\", data=null und meta.covered_cities (Liste der abgedeckten Städte), klar unterscheidbar von no_data (Connector vorhanden, aber gerade keine Ereignisse). Tier A. Deaktiviert -> 200 source_status=\"disabled\". Toter Upstream ohne Cache -> 503 mit Hint.",
        "x-description-en": "Returns a city's inner-city roadworks and closures from the keyless per-city sources (Berlin VIZ, Hamburg, Cologne, Munich, MobiData BW, Dortmund, SPERRINFOSYS Saxony) in the canonical envelope. Events carry canonical English field names (e.g. description, event_type, start, end, street); the equivalent German fields of the city sources (e.g. beschreibung, art, von, bis, strasse) are deprecated and will be removed no earlier than 30 days after the announcement. Partial coverage (curated connectors): only berlin, hamburg, koeln, muenchen, stuttgart, dortmund, bremen, dresden, leipzig return data. A city without a connector honestly responds with 200 source_status=\"not_covered\", data=null and meta.covered_cities (the list of covered cities), clearly distinct from no_data (connector present, but currently no events). Tier A. Disabled -> 200 source_status=\"disabled\". Dead upstream with no cache -> 503 with a hint.",
        "operationId": "getCityRoadEvents",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Road-Event-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/events": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Veranstaltungen je Stadt (destination.one)",
        "x-summary-en": "Events per city (destination.one)",
        "description": "Aktuelle und kommende Veranstaltungen einer Stadt aus der account-gebundenen Quelle destination.one/eT4.META im kanonischen Envelope. Die Lizenz wird je Datensatz aus dem Lizenzfeld abgeleitet (CC0/CC-BY, CC-BY-SA, sonst unbekannt) und in attribution gespiegelt. Vergangene Termine werden herausgefiltert. Sonderfälle: ist die Quelle aus oder fehlt der licensekey, kommt 200 mit source_status=\"disabled\"; liefert die Quelle nichts Aktuelles, kommt 200 mit source_status=\"no_data\"; toter Upstream ohne Cache liefert 503 mit Hint.",
        "x-description-en": "Current and upcoming events for a city from the account-bound source destination.one/eT4.META in the canonical envelope. The license is derived per record from the license field (CC0/CC-BY, CC-BY-SA, otherwise unknown) and mirrored in attribution. Past dates are filtered out. Special cases: if the source is off or the licensekey is missing it returns 200 with source_status=\"disabled\"; if the source has nothing current it returns 200 with source_status=\"no_data\"; a dead upstream with no cache returns 503 with a hint.",
        "operationId": "getCityEvents",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Event-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities/{slug}/webcams": {
      "get": {
        "tags": [
          "cities"
        ],
        "summary": "Autobahn-Webcams je Stadt (Autobahn-API, Tier A)",
        "x-summary-en": "Autobahn webcams per city (Autobahn API, Tier A)",
        "description": "Liefert Autobahn-Webcams (Koordinaten und Bild-URLs) im Umfeld einer Stadt aus dem schlüssellosen Autobahn-Webcam-Service. Die Webcams werden per Bounding-Box um die Stadt gefiltert. Tier A. Flächendeckend: jeder der 84 Städte sind die nahen Autobahnen kuratiert zugeordnet; eine Stadt ohne Webcam antwortet ehrlich mit no_data. Weitere Sonderfälle: deaktiviert -> 200 source_status=\"disabled\"; toter Upstream ohne Cache -> 503 mit Hint. Hinweis: Die Autobahn-Webcam-Quelle liefert aktuell bundesweit keine Webcams, daher antwortet der Endpunkt derzeit durchgehend mit no_data.",
        "x-description-en": "Returns Autobahn webcams (coordinates and image URLs) around a city from the keyless Autobahn webcam service. The webcams are filtered by a bounding box around the city. Tier A. Full coverage: each of the 84 cities has its nearby motorways curated; a city with no webcam honestly responds with no_data. Other special cases: disabled -> 200 source_status=\"disabled\"; dead upstream with no cache -> 503 with a hint. Note: the Autobahn webcam source currently returns no webcams nationwide, so this endpoint currently always responds with no_data.",
        "operationId": "getCityWebcams",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Webcam-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/{city}/traffic-flow": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-Verkehrslage je Stadt (Mobilithek DATEX-II)",
        "x-summary-en": "Live traffic flow per city (Mobilithek DATEX-II)",
        "description": "Liefert die minutenfrische Verkehrslage einer Stadt aus der Mobilithek (DATEX-II) im Live-Envelope. Der meta-Block trägt zusätzlich as_of (Datenstand) und refresh_seconds (Aktualisierungstakt). Solange Zertifikat und Abo noch nicht gesetzt sind -> 200 source_status=\"disabled\" (nie 5xx); kein Datenpaket -> 200 source_status=\"no_data\".",
        "x-description-en": "Returns a city's minute-fresh traffic flow from the Mobilithek (DATEX-II) in the live envelope. The meta block additionally carries as_of (data timestamp) and refresh_seconds (update cadence). As long as the certificate and subscription are not set up -> 200 source_status=\"disabled\" (never 5xx); no data packet -> 200 source_status=\"no_data\".",
        "operationId": "getLiveTrafficFlow",
        "parameters": [
          {
            "name": "city",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "berlin"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Live-Verkehrslage-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/{city}/roadworks": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-Baustellen je Stadt (Mobilithek DATEX-II)",
        "x-summary-en": "Live roadworks per city (Mobilithek DATEX-II)",
        "description": "Liefert die Baustellen einer Stadt aus der Mobilithek (DATEX-II SituationPublication) im Live-Envelope. Der meta-Block trägt zusätzlich as_of (Datenstand) und refresh_seconds (Aktualisierungstakt). Solange Zertifikat und Abo nicht gesetzt sind -> 200 source_status=\"disabled\" (nie 5xx); kein Datenpaket -> 200 source_status=\"no_data\". Reine Live-Daten, nur im Redis-Cache gehalten.",
        "x-description-en": "Returns a city's roadworks from the Mobilithek (DATEX-II SituationPublication) in the live envelope. The meta block additionally carries as_of (data timestamp) and refresh_seconds (update cadence). As long as the certificate and subscription are not set up -> 200 source_status=\"disabled\" (never 5xx); no data packet -> 200 source_status=\"no_data\". Live data only, kept in the Redis cache.",
        "operationId": "getLiveRoadworks",
        "parameters": [
          {
            "name": "city",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "bremen"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Live-Baustellen-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/{city}/baustellen": {
      "get": {
        "tags": [
          "Live"
        ],
        "deprecated": true,
        "summary": "[DEPRECATED] Live-Baustellen je Stadt (Nachfolger: /live/{city}/roadworks)",
        "x-summary-en": "[DEPRECATED] Live roadworks per city (successor: /live/{city}/roadworks)",
        "description": "DEPRECATED (englische Slugs): abgelöst vom kanonischen Pfad GET /api/v1/live/{city}/roadworks. Dieser Pfad bleibt rückwärtskompatibel (unveränderter Envelope) und trägt Deprecation-/Link-Header auf den Nachfolger. Bitte auf den neuen Pfad migrieren.",
        "x-description-en": "DEPRECATED (English slugs): superseded by the canonical path GET /api/v1/live/{city}/roadworks. This path stays backward-compatible (unchanged envelope) and carries Deprecation/Link headers pointing to the successor. Please migrate to the new path.",
        "operationId": "getLiveBaustellen",
        "parameters": [
          {
            "name": "city",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "bremen"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Live-Baustellen-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/{city}/incidents": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-Verkehrsereignisse je Stadt (Mobilithek DATEX-II)",
        "x-summary-en": "Live traffic events per city (Mobilithek DATEX-II)",
        "description": "Liefert die Verkehrsereignisse einer Stadt aus der Mobilithek (DATEX-II SituationPublication) im Live-Envelope. Der meta-Block trägt zusätzlich as_of (Datenstand) und refresh_seconds (Aktualisierungstakt). Solange Zertifikat und Abo nicht gesetzt sind -> 200 source_status=\"disabled\" (nie 5xx); kein Datenpaket -> 200 source_status=\"no_data\". Reine Live-Daten, nur im Redis-Cache gehalten.",
        "x-description-en": "Returns a city's traffic events from the Mobilithek (DATEX-II SituationPublication) in the live envelope. The meta block additionally carries as_of (data timestamp) and refresh_seconds (update cadence). As long as the certificate and subscription are not set up -> 200 source_status=\"disabled\" (never 5xx); no data packet -> 200 source_status=\"no_data\". Live data only, kept in the Redis cache.",
        "operationId": "getLiveIncidents",
        "parameters": [
          {
            "name": "city",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "koeln"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Live-Ereignisse-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/{city}/ereignisse": {
      "get": {
        "tags": [
          "Live"
        ],
        "deprecated": true,
        "summary": "[DEPRECATED] Live-Verkehrsereignisse je Stadt (Nachfolger: /live/{city}/incidents)",
        "x-summary-en": "[DEPRECATED] Live traffic events per city (successor: /live/{city}/incidents)",
        "description": "DEPRECATED (englische Slugs): abgelöst vom kanonischen Pfad GET /api/v1/live/{city}/incidents. Dieser Pfad bleibt rückwärtskompatibel (unveränderter Envelope) und trägt Deprecation-/Link-Header auf den Nachfolger. Bitte auf den neuen Pfad migrieren.",
        "x-description-en": "DEPRECATED (English slugs): superseded by the canonical path GET /api/v1/live/{city}/incidents. This path stays backward-compatible (unchanged envelope) and carries Deprecation/Link headers pointing to the successor. Please migrate to the new path.",
        "operationId": "getLiveEreignisse",
        "parameters": [
          {
            "name": "city",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "koeln"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Live-Ereignisse-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/berlin/traffic-reports": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-Verkehrsmeldungen Berlin (Mobilithek DATEX-II)",
        "x-summary-en": "Live traffic messages for Berlin (Mobilithek DATEX-II)",
        "description": "Liefert die Berlin-Verkehrsmeldungen (SenMVKU, DATEX-II SituationPublication) im Live-Envelope. Der meta-Block trägt zusätzlich as_of (Datenstand) und refresh_seconds (Aktualisierungstakt). Solange Zertifikat und Abo nicht gesetzt sind -> 200 source_status=\"disabled\" (nie 5xx); kein Datenpaket -> 200 source_status=\"no_data\". Reine Live-Daten, nur im Redis-Cache gehalten.",
        "x-description-en": "Returns Berlin traffic messages (SenMVKU, DATEX-II SituationPublication) in the live envelope. The meta block additionally carries as_of (data timestamp) and refresh_seconds (update cadence). As long as the certificate and subscription are not set up -> 200 source_status=\"disabled\" (never 5xx); no data packet -> 200 source_status=\"no_data\". Live data only, kept in the Redis cache.",
        "operationId": "getLiveBerlinTrafficReports",
        "responses": {
          "200": {
            "description": "Live-Verkehrsmeldungen-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/berlin/verkehrsmeldungen": {
      "get": {
        "tags": [
          "Live"
        ],
        "deprecated": true,
        "summary": "[DEPRECATED] Live-Verkehrsmeldungen Berlin (Nachfolger: /live/berlin/traffic-reports)",
        "x-summary-en": "[DEPRECATED] Live traffic messages for Berlin (successor: /live/berlin/traffic-reports)",
        "description": "DEPRECATED (englische Slugs): abgelöst vom kanonischen Pfad GET /api/v1/live/berlin/traffic-reports. Dieser Pfad bleibt rückwärtskompatibel (unveränderter Envelope) und trägt Deprecation-/ Link-Header auf den Nachfolger. Bitte auf den neuen Pfad migrieren.",
        "x-description-en": "DEPRECATED (English slugs): superseded by the canonical path GET /api/v1/live/berlin/traffic-reports. This path stays backward-compatible (unchanged envelope) and carries Deprecation/Link headers pointing to the successor. Please migrate to the new path.",
        "operationId": "getLiveBerlinVerkehrsmeldungen",
        "responses": {
          "200": {
            "description": "Live-Verkehrsmeldungen-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/koeln/low-emission-zone": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-Umweltzone Köln (Mobilithek DATEX-II)",
        "x-summary-en": "Live low-emission zone for Cologne (Mobilithek DATEX-II)",
        "description": "Liefert die Köln-Umweltzone (LowEmissionZone, MoCKiii, DATEX-II SituationPublication) im Live-Envelope. Der meta-Block trägt zusätzlich as_of (Datenstand) und refresh_seconds (Aktualisierungstakt). Solange Zertifikat und Abo nicht gesetzt sind -> 200 source_status=\"disabled\" (nie 5xx); kein Datenpaket -> 200 source_status=\"no_data\". Reine Live-Daten, nur im Redis-Cache gehalten.",
        "x-description-en": "Returns the Cologne low-emission zone (MoCKiii, DATEX-II SituationPublication) in the live envelope. The meta block additionally carries as_of (data timestamp) and refresh_seconds (update cadence). As long as the certificate and subscription are not set up -> 200 source_status=\"disabled\" (never 5xx); no data packet -> 200 source_status=\"no_data\". Live data only, kept in the Redis cache.",
        "operationId": "getLiveKoelnLowEmissionZone",
        "responses": {
          "200": {
            "description": "Live-Umweltzone-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/koeln/umweltzone": {
      "get": {
        "tags": [
          "Live"
        ],
        "deprecated": true,
        "summary": "[DEPRECATED] Live-Umweltzone Köln (Nachfolger: /live/koeln/low-emission-zone)",
        "x-summary-en": "[DEPRECATED] Live low-emission zone for Cologne (successor: /live/koeln/low-emission-zone)",
        "description": "DEPRECATED (englische Slugs): abgelöst vom kanonischen Pfad GET /api/v1/live/koeln/low-emission-zone. Dieser Pfad bleibt rückwärtskompatibel (unveränderter Envelope) und trägt Deprecation-/ Link-Header auf den Nachfolger. Bitte auf den neuen Pfad migrieren.",
        "x-description-en": "DEPRECATED (English slugs): superseded by the canonical path GET /api/v1/live/koeln/low-emission-zone. This path stays backward-compatible (unchanged envelope) and carries Deprecation/Link headers pointing to the successor. Please migrate to the new path.",
        "operationId": "getLiveKoelnUmweltzone",
        "responses": {
          "200": {
            "description": "Live-Umweltzone-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/hannover/traffic-reports": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-Verkehrsmeldungen Hannover (Mobilithek DATEX-II)",
        "x-summary-en": "Live traffic messages for Hanover (Mobilithek DATEX-II)",
        "description": "Liefert die Hannover-Verkehrsmeldungen (Landeshauptstadt Hannover, Fachbereich Tiefbau: Baustellen, verkehrsrelevante Veranstaltungen, Verkehrsstörungen, DATEX-II SituationPublication) im Live-Envelope. Der meta-Block trägt zusätzlich as_of (Datenstand) und refresh_seconds (Aktualisierungstakt). Solange Zertifikat und Abo nicht gesetzt sind -> 200 source_status=\"disabled\" (nie 5xx); kein Datenpaket -> 200 source_status=\"no_data\". Reine Live-Daten, nur im Redis-Cache gehalten.",
        "x-description-en": "Returns Hanover traffic messages (City of Hanover, road construction department: roadworks, traffic-relevant events, traffic disruptions, DATEX-II SituationPublication) in the live envelope. The meta block additionally carries as_of (data timestamp) and refresh_seconds (update cadence). As long as the certificate and subscription are not set up -> 200 source_status=\"disabled\" (never 5xx); no data packet -> 200 source_status=\"no_data\". Live data only, kept in the Redis cache.",
        "operationId": "getLiveHannoverTrafficReports",
        "responses": {
          "200": {
            "description": "Live-Verkehrsmeldungen-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/hannover/verkehrsmeldungen": {
      "get": {
        "tags": [
          "Live"
        ],
        "deprecated": true,
        "summary": "[DEPRECATED] Live-Verkehrsmeldungen Hannover (Nachfolger: /live/hannover/traffic-reports)",
        "x-summary-en": "[DEPRECATED] Live traffic messages for Hanover (successor: /live/hannover/traffic-reports)",
        "description": "DEPRECATED (englische Slugs): abgelöst vom kanonischen Pfad GET /api/v1/live/hannover/traffic-reports. Dieser Pfad bleibt rückwärtskompatibel (unveränderter Envelope) und trägt Deprecation-/ Link-Header auf den Nachfolger. Bitte auf den neuen Pfad migrieren.",
        "x-description-en": "DEPRECATED (English slugs): superseded by the canonical path GET /api/v1/live/hannover/traffic-reports. This path stays backward-compatible (unchanged envelope) and carries Deprecation/Link headers pointing to the successor. Please migrate to the new path.",
        "operationId": "getLiveHannoverVerkehrsmeldungen",
        "responses": {
          "200": {
            "description": "Live-Verkehrsmeldungen-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/dortmund/parking": {
      "get": {
        "tags": [
          "Live"
        ],
        "deprecated": true,
        "summary": "[DEPRECATED] Live-Parkbelegung Dortmund (Nachfolger: /cities/dortmund/parking)",
        "x-summary-en": "[DEPRECATED] Live parking occupancy Dortmund (successor: /cities/dortmund/parking)",
        "description": "DEPRECATED (DATA-40): abgelöst vom vereinheitlichten Endpunkt GET /api/v1/cities/dortmund/parking (EIN Parking-Endpunkt für alle Städte mit Quellen-Fallback). Dieser Pfad bleibt rückwärtskompatibel und trägt Deprecation-/Link-Header auf den Nachfolger. Liefert die Dortmund-Parkbelegung (dynamisches Parkleitsystem, Parkhäuser und Park&Ride) im Live-Envelope. Quelle ist der direkte, keylose offene Datensatz der Stadt Dortmund (open-data.dortmund.de), nicht die Mobilithek. Der meta-Block trägt zusätzlich as_of (Datenstand) und refresh_seconds (Aktualisierungstakt). Quelle deaktiviert -> 200 source_status=\"disabled\" (nie 5xx); keine Daten -> 200 source_status=\"no_data\". Lizenz Datenlizenz Deutschland Zero 2.0. Reine Live-Daten, nur im Redis-Cache gehalten.",
        "x-description-en": "DEPRECATED (DATA-40): superseded by the unified endpoint GET /api/v1/cities/dortmund/parking (one parking endpoint for all cities with a source fallback). This path stays backward-compatible and carries Deprecation/Link headers pointing to the successor. Returns Dortmund parking occupancy (dynamic parking guidance system, car parks and park&ride) in the live envelope. Source is the City of Dortmund's direct, key-less open dataset (open-data.dortmund.de), not the Mobilithek. The meta block additionally carries as_of (data timestamp) and refresh_seconds (update cadence). Source disabled -> 200 source_status=\"disabled\" (never 5xx); no data -> 200 source_status=\"no_data\". Licence Data licence Germany Zero 2.0. Live data only, kept in the Redis cache.",
        "operationId": "getLiveDortmundParking",
        "responses": {
          "200": {
            "description": "Live-Parkbelegung-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/frankfurt-am-main/parking": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-Parkbelegung Frankfurt am Main (Stadt Frankfurt, Mobilithek DATEX II V3)",
        "x-summary-en": "Live parking occupancy for Frankfurt am Main (City of Frankfurt, Mobilithek DATEX II V3)",
        "description": "Liefert die Parkbelegung der Stadt Frankfurt am Main im Live-Envelope: je Parkplatz freie Plätze, Auslastung in Prozent und gestufte Auslastung (facilities), angereichert um Name, Geo-Koordinate und Kapazität aus den statischen Stammdaten. Quelle sind zwei Mobilithek-Abos (DATEX II V3, dynamisch + statisch), die der Adapter über die parkingRecord-ID joint. Der meta-Block trägt zusätzlich as_of (Datenstand) und refresh_seconds (Aktualisierungstakt). Quelle deaktiviert -> 200 source_status=\"disabled\" (nie 5xx); keine Daten -> 200 source_status=\"no_data\". Lizenz Datenlizenz Deutschland Namensnennung 2.0. Reine Live-Daten, nur im Redis-Cache.",
        "x-description-en": "Returns the City of Frankfurt am Main parking occupancy in the live envelope: per car park the vacant spaces, occupancy percentage and graded occupancy (facilities), enriched with name, geo coordinate and capacity from the static master data. Source is two Mobilithek subscriptions (DATEX II V3, dynamic + static) joined by the adapter via the parkingRecord id. The meta block additionally carries as_of (data timestamp) and refresh_seconds (update cadence). Source disabled -> 200 source_status=\"disabled\" (never 5xx); no data -> 200 source_status=\"no_data\". Licence Data licence Germany Attribution 2.0. Live data only, kept in the Redis cache.",
        "operationId": "getLiveFrankfurtParking",
        "responses": {
          "200": {
            "description": "Live-Parkbelegung-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/magdeburg/parking": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-Parkbelegung Magdeburg (Landeshauptstadt Magdeburg, Mobilithek DATEX II V2)",
        "x-summary-en": "Live parking occupancy for Magdeburg (City of Magdeburg, Mobilithek DATEX II V2)",
        "description": "Liefert die Parkbelegung der Landeshauptstadt Magdeburg im Live-Envelope: je Parkplatz freie Plätze, Auslastung in Prozent, Belegungstrend und Status (facilities), angereichert um Name, Geo-Koordinate und Kapazität aus den statischen Stammdaten. Quelle sind zwei Mobilithek-Abos (DATEX II V2 ParkingFacility, dynamisch + statisch, Anbieter ifak e.V.), die der Adapter über die parkingFacility-ID joint. Der meta-Block trägt zusätzlich as_of (Datenstand) und refresh_seconds (Aktualisierungstakt). Quelle deaktiviert -> 200 source_status=\"disabled\" (nie 5xx); keine Daten -> 200 source_status=\"no_data\". Lizenz freie Nutzung/Open Data (keine benannte Standardlizenz). Reine Live-Daten, nur im Redis-Cache gehalten.",
        "x-description-en": "Returns the City of Magdeburg parking occupancy in the live envelope: per car park the vacant spaces, occupancy percentage, occupancy trend and status (facilities), enriched with name, geo coordinate and capacity from the static master data. Source is two Mobilithek subscriptions (DATEX II V2 ParkingFacility, dynamic + static, provider ifak e.V.) joined by the adapter via the parkingFacility id. The meta block additionally carries as_of (data timestamp) and refresh_seconds (update cadence). Source disabled -> 200 source_status=\"disabled\" (never 5xx); no data -> 200 source_status=\"no_data\". Licence free use/Open Data (no named standard licence). Live data only, kept in the Redis cache.",
        "operationId": "getLiveMagdeburgParking",
        "responses": {
          "200": {
            "description": "Live-Parkbelegung-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/wuppertal/parking": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-Parkbelegung Wuppertal (Stadt Wuppertal, Mobilithek DATEX II V2)",
        "x-summary-en": "Live parking occupancy for Wuppertal (City of Wuppertal, Mobilithek DATEX II V2)",
        "description": "Liefert die Parkbelegung der Stadt Wuppertal im Live-Envelope: je Parkplatz freie Plätze, Auslastung in Prozent, Belegungstrend und Status (facilities), angereichert um Name, Geo-Koordinate und Kapazität aus den statischen Stammdaten. Quelle sind zwei Mobilithek-Abos (DATEX II V2 ParkingFacility, dynamisch + statisch), die der Adapter über die parkingFacility-ID joint. Der meta-Block trägt zusätzlich as_of (Datenstand) und refresh_seconds (Aktualisierungstakt). Quelle deaktiviert -> 200 source_status=\"disabled\" (nie 5xx); keine Daten -> 200 source_status=\"no_data\". Lizenz Datenlizenz Deutschland Zero 2.0. Reine Live-Daten, nur im Redis-Cache gehalten.",
        "x-description-en": "Returns the City of Wuppertal parking occupancy in the live envelope: per car park the vacant spaces, occupancy percentage, occupancy trend and status (facilities), enriched with name, geo coordinate and capacity from the static master data. Source is two Mobilithek subscriptions (DATEX II V2 ParkingFacility, dynamic + static) joined by the adapter via the parkingFacility id. The meta block additionally carries as_of (data timestamp) and refresh_seconds (update cadence). Source disabled -> 200 source_status=\"disabled\" (never 5xx); no data -> 200 source_status=\"no_data\". Licence Data licence Germany Zero 2.0. Live data only, kept in the Redis cache.",
        "operationId": "getLiveWuppertalParking",
        "responses": {
          "200": {
            "description": "Live-Parkbelegung-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/hamburg/traffic-situation": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-Verkehrslage Hamburg (Freie und Hansestadt Hamburg, keylos)",
        "x-summary-en": "Live traffic situation for Hamburg (City of Hamburg open data, key-less)",
        "description": "Liefert die Hamburger Echtzeit-Verkehrslage im Live-Envelope: eine Netz-Zusammenfassung (summary.total und by_state-Zählung je Zustandsklasse fließend/dicht/zäh/gestaut) sowie die priorisierte, gedeckelte Liste der nicht-fließenden Straßenabschnitte (measurements mit Zustand und Mittelpunkt-Koordinate). Quelle ist der direkte, keylose offene Datensatz der Freien und Hansestadt Hamburg (api.hamburg.de, OGC API Features), nicht die Mobilithek. Der meta-Block trägt zusätzlich as_of (Datenstand) und refresh_seconds (Aktualisierungstakt). Quelle deaktiviert -> 200 source_status=\"disabled\" (nie 5xx); keine Daten -> 200 source_status=\"no_data\". Lizenz Datenlizenz Deutschland Namensnennung 2.0. Reine Live-Daten, nur im Redis-Cache gehalten.",
        "x-description-en": "Returns Hamburg's real-time traffic situation in the live envelope: a network summary (summary.total and by_state counts per state free/dense/sluggish/jammed) plus the prioritised, capped list of non-free-flowing road segments (measurements with state and midpoint coordinate). Source is the City of Hamburg's direct, key-less open dataset (api.hamburg.de, OGC API Features), not the Mobilithek. The meta block additionally carries as_of (data timestamp) and refresh_seconds (update cadence). Source disabled -> 200 source_status=\"disabled\" (never 5xx); no data -> 200 source_status=\"no_data\". Licence Data licence Germany Attribution 2.0. Live data only, kept in the Redis cache.",
        "operationId": "getLiveHamburgTrafficSituation",
        "responses": {
          "200": {
            "description": "Live-Verkehrslage-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/hamburg/verkehrslage": {
      "get": {
        "tags": [
          "Live"
        ],
        "deprecated": true,
        "summary": "[DEPRECATED] Live-Verkehrslage Hamburg (Nachfolger: /live/hamburg/traffic-situation)",
        "x-summary-en": "[DEPRECATED] Live traffic situation for Hamburg (successor: /live/hamburg/traffic-situation)",
        "description": "DEPRECATED (englische Slugs): abgelöst vom kanonischen Pfad GET /api/v1/live/hamburg/traffic-situation. Dieser Pfad bleibt rückwärtskompatibel (unveränderter Envelope) und trägt Deprecation-/ Link-Header auf den Nachfolger. Bitte auf den neuen Pfad migrieren.",
        "x-description-en": "DEPRECATED (English slugs): superseded by the canonical path GET /api/v1/live/hamburg/traffic-situation. This path stays backward-compatible (unchanged envelope) and carries Deprecation/Link headers pointing to the successor. Please migrate to the new path.",
        "operationId": "getLiveHamburgVerkehrslage",
        "responses": {
          "200": {
            "description": "Live-Verkehrslage-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/kiel/counting-stations": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-Zähldaten Kiel, Kfz- und Radzählstellen (Mobilithek DATEX-II)",
        "x-summary-en": "Live count data for Kiel, vehicle and bicycle counters (Mobilithek DATEX-II)",
        "description": "Liefert die Kiel-Zähldaten (Kfz-Dauerzählstellen und Radzähler, DATEX-II MeasuredDataPublication) im Live-Envelope. Der meta-Block trägt zusätzlich as_of (Datenstand) und refresh_seconds (Aktualisierungstakt). Solange Zertifikat und Abo nicht gesetzt sind -> 200 source_status=\"disabled\" (nie 5xx); kein Datenpaket -> 200 source_status=\"no_data\". Reine Live-Daten, nur im Redis-Cache gehalten.",
        "x-description-en": "Returns Kiel count data (permanent motor-vehicle counters and bicycle counters, DATEX-II MeasuredDataPublication) in the live envelope. The meta block additionally carries as_of (data timestamp) and refresh_seconds (update cadence). As long as the certificate and subscription are not set up -> 200 source_status=\"disabled\" (never 5xx); no data packet -> 200 source_status=\"no_data\". Live data only, kept in the Redis cache.",
        "operationId": "getLiveKielCountingStations",
        "responses": {
          "200": {
            "description": "Live-Zähldaten-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/kiel/zaehlstellen": {
      "get": {
        "tags": [
          "Live"
        ],
        "deprecated": true,
        "summary": "[DEPRECATED] Live-Zähldaten Kiel (Nachfolger: /live/kiel/counting-stations)",
        "x-summary-en": "[DEPRECATED] Live count data for Kiel (successor: /live/kiel/counting-stations)",
        "description": "DEPRECATED (englische Slugs): abgelöst vom kanonischen Pfad GET /api/v1/live/kiel/counting-stations. Dieser Pfad bleibt rückwärtskompatibel (unveränderter Envelope) und trägt Deprecation-/ Link-Header auf den Nachfolger. Bitte auf den neuen Pfad migrieren.",
        "x-description-en": "DEPRECATED (English slugs): superseded by the canonical path GET /api/v1/live/kiel/counting-stations. This path stays backward-compatible (unchanged envelope) and carries Deprecation/Link headers pointing to the successor. Please migrate to the new path.",
        "operationId": "getLiveKielZaehlstellen",
        "responses": {
          "200": {
            "description": "Live-Zähldaten-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/eround/charging": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-Ladesäulen-Belegung eRound (Mobilithek DATEX-II V3)",
        "x-summary-en": "Live EV charging-station occupancy via eRound (Mobilithek DATEX-II V3)",
        "description": "Liefert das jüngste eRound-Belegungs-Delta (AFIR-Recharging, DATEX-II V3 EnergyInfrastructureStatusPublication, JSON-Syntax) im Live-Envelope. Der Feed wird ausschließlich von einem Hintergrund-Poller gepullt (alle 5 Minuten); dieser Endpunkt liest nur dessen Redis-Snapshot, es findet kein Upstream-Abruf im Request statt. Der meta-Block trägt zusätzlich as_of (Datenstand) und refresh_seconds (Poll-Takt). Lizenz Creative Commons CC Zero (Tier A). Ohne Zertifikat/Abo -> 200 source_status=\"disabled\" (nie 5xx); kein/abgelaufener Snapshot oder leeres Delta -> 200 source_status=\"no_data\". Reine Live-Daten, nur in Redis gehalten. Je-Stadt-Aggregation: /cities/{slug}/charging-status.",
        "x-description-en": "Returns the most recent eRound occupancy delta (AFIR recharging, DATEX-II V3 EnergyInfrastructureStatusPublication, JSON syntax) in the live envelope. The feed is pulled exclusively by a background poller (every 5 minutes); this endpoint only reads its Redis snapshot, no upstream call happens at request time. The meta block additionally carries as_of (data timestamp) and refresh_seconds (poll cadence). License Creative Commons CC Zero (Tier A). Without certificate/ subscription -> 200 source_status=\"disabled\" (never 5xx); missing/ expired snapshot or an empty delta -> 200 source_status=\"no_data\". Live data only, kept in Redis. Per-city aggregation: /cities/{slug}/charging-status.",
        "operationId": "getLiveEroundCharging",
        "responses": {
          "200": {
            "description": "Live-Ladesäulen-Belegung-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/hamburg/departures": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-ÖPNV-Abfahrten Hamburg je Station (HVV-Geofox-GTI)",
        "x-summary-en": "Live public-transit departures for Hamburg per station (HVV Geofox GTI)",
        "description": "Liefert die Echtzeit-Abfahrtstafel einer HVV-Station aus der HVV-Geofox-GTI-API: je Abfahrt Linie, Richtung, Soll-Offset in Minuten, Verspätung in Sekunden und Linien-Störungshinweise. Stadt fix hamburg (Geofox deckt nur den HVV-Raum ab). Der Query-Parameter station ist ein Stationsname (Default Hauptbahnhof), der intern via checkName auf die Geofox-Station-ID aufgelöst wird. Der meta-Block trägt zusätzlich as_of und refresh_seconds (60). Tier C live-only: die Geofox-Lizenz ist nicht offen (source_status der Quelle = unknown), reine Live-Daten, nur kurzlebig im Redis-Cache gehalten. Toggle aus oder fehlende Credentials -> 200 source_status=\"disabled\"; unbekannte Station oder keine Abfahrten -> 200 source_status=\"no_data\"; toter Upstream ohne Cache -> 503 mit Hint.",
        "x-description-en": "Returns the real-time departure board of an HVV station from the HVV Geofox GTI API: per departure the line, direction, scheduled offset in minutes, delay in seconds and per-line service alerts. City fixed to hamburg (Geofox only covers the HVV area). The station query parameter is a station name (default Hauptbahnhof), resolved internally via checkName to the Geofox station id. The meta block additionally carries as_of and refresh_seconds (60). Tier C live-only: the Geofox license is not open (source license = unknown), live data only, kept only briefly in the Redis cache. Disabled or missing credentials -> 200 source_status=\"disabled\"; unknown station or no departures -> 200 source_status=\"no_data\"; dead upstream with no cache -> 503 with a hint.",
        "operationId": "getLiveHamburgDepartures",
        "parameters": [
          {
            "name": "station",
            "in": "query",
            "required": false,
            "description": "HVV-Stationsname (Default \"Hamburg Hauptbahnhof\").",
            "schema": {
              "type": "string",
              "default": "Hamburg Hauptbahnhof"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Live-Abfahrten-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ungültiger oder fehlender station-Parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/frankfurt-am-main/departures": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-ÖPNV-Abfahrten Frankfurt/Rhein-Main je Station (RMV-HAPI)",
        "x-summary-en": "Live public-transit departures for Frankfurt/Rhine-Main per station (RMV HAPI)",
        "description": "Liefert die Echtzeit-Abfahrtstafel einer Rhein-Main-Station aus der RMV-HAPI (HAFAS-ReST, www.rmv.de/hapi): je Abfahrt Linie, Richtung, Minuten bis zur Abfahrt und Verspätung in Sekunden. Stadt fix frankfurt-am-main (RMV deckt den Rhein-Main-Raum ab). Der Query-Parameter station ist ein Stationsname (Default \"Frankfurt (Main) Hauptbahnhof\"), der intern via location.name auf die HAFAS-Station-ID aufgelöst wird. Der meta-Block trägt zusätzlich as_of und refresh_seconds (60). Tier C live-only: die RMV-HAPI ist registrierungspflichtig, die Lizenz nicht offen (source license = unknown), reine Live-Daten, nur kurzlebig im Redis-Cache gehalten. Toggle aus oder fehlender accessId -> 200 source_status=\"disabled\"; unbekannte Station oder keine Abfahrten -> 200 source_status=\"no_data\"; toter Upstream ohne Cache -> 503 mit Hint.",
        "x-description-en": "Returns the real-time departure board of a Rhine-Main station from the RMV HAPI (HAFAS ReST, www.rmv.de/hapi): per departure the line, direction, minutes until departure and delay in seconds. City fixed to frankfurt-am-main (RMV covers the Rhine-Main area). The station query parameter is a station name (default \"Frankfurt (Main) Hauptbahnhof\"), resolved internally via location.name to the HAFAS station id. The meta block additionally carries as_of and refresh_seconds (60). Tier C live-only: the RMV HAPI requires registration, the license is not open (source license = unknown), live data only, kept only briefly in the Redis cache. Disabled or missing accessId -> 200 source_status=\"disabled\"; unknown station or no departures -> 200 source_status=\"no_data\"; dead upstream with no cache -> 503 with a hint.",
        "operationId": "getLiveFrankfurtDepartures",
        "parameters": [
          {
            "name": "station",
            "in": "query",
            "required": false,
            "description": "RMV-Stationsname (Default \"Frankfurt (Main) Hauptbahnhof\").",
            "schema": {
              "type": "string",
              "default": "Frankfurt (Main) Hauptbahnhof"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Live-Abfahrten-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ungültiger oder fehlender station-Parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/nuernberg/departures": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-ÖPNV-Abfahrten Nürnberg je Halt (VGN/VAG, keylos, Tier A)",
        "x-summary-en": "Live public-transit departures for Nuremberg per stop (VGN/VAG, key-less, Tier A)",
        "description": "Liefert die Echtzeit-Abfahrtstafel eines VGN-Halts aus der offenen, keylosen VAG-Puls-API (start.vag.de): je Abfahrt Linie, Richtung, Soll-Offset in Minuten, Verspätung in Sekunden und Produkt. Stadt fix nuernberg (VGN-Raum). Der Query-Parameter stop_id ist die numerische VGN-Halt-ID (VGNKennung, Default 510 = Nürnberg Hbf). Der meta-Block trägt zusätzlich as_of und refresh_seconds (60). Tier A: offene Lizenz Creative Commons Attribution 4.0 (opendata.vag.de), sauber verwertbar. Reine Live-Daten, nur kurzlebig im Redis-Cache gehalten. Toggle aus -> 200 source_status=\"disabled\"; ungültige stop_id -> 400; keine Abfahrten -> 200 source_status=\"no_data\"; toter Upstream ohne Cache -> 503 mit Hint.",
        "x-description-en": "Returns the real-time departure board of a VGN stop from the open, key-less VAG Puls API (start.vag.de): per departure the line, direction, scheduled offset in minutes, delay in seconds and product. City fixed to nuernberg (VGN area). The stop_id query parameter is the numeric VGN stop id (VGNKennung, default 510 = Nuremberg Hbf). The meta block additionally carries as_of and refresh_seconds (60). Tier A: open licence Creative Commons Attribution 4.0 (opendata.vag.de), freely reusable. Live data only, kept only briefly in the Redis cache. Disabled -> 200 source_status=\"disabled\"; invalid stop_id -> 400; no departures -> 200 source_status=\"no_data\"; dead upstream with no cache -> 503 with a hint.",
        "operationId": "getLiveNuernbergDepartures",
        "parameters": [
          {
            "name": "stop_id",
            "in": "query",
            "required": false,
            "description": "Numerische VGN-Halt-ID (VGNKennung), Default \"510\" (Nürnberg Hbf).",
            "schema": {
              "type": "string",
              "default": "510"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Live-Abfahrten-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ungültige oder fehlende stop_id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/{slug}/departures": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-ÖPNV-Abfahrten der VRR-Kernstädte + VVS Stuttgart je Station (Mentz-EFA, Tier C)",
        "x-summary-en": "Live public-transit departures for the VRR core cities + VVS Stuttgart per station (Mentz EFA, Tier C)",
        "description": "Liefert die Echtzeit-Abfahrtstafel einer Station in einer der sechs VRR-Kernstädte (duesseldorf, dortmund, essen, duisburg, bochum, wuppertal, keylose Mentz-EFA efa.vrr.de) oder in Stuttgart (VVS, keylose Mentz-EFA www3.vvs.de): je Abfahrt Linie, Richtung, Minuten bis zur Abfahrt und Verspätung in Sekunden. Der Slug wählt die Stadt (Stadt-Scoping über das AGS5-Präfix); ein Nicht-EFA-Slug liefert 404. Der Query-Parameter station ist ein Stationsname (Default Hauptbahnhof der Stadt), intern via StopFinder auf die EFA-Global-ID aufgelöst. Der meta-Block trägt zusätzlich as_of und refresh_seconds (60). Tier C live-only: die EFA-Lizenz ist nicht klar offen (source license = unknown), reine Live-Daten, nur kurzlebig im Redis-Cache gehalten. Attribution je Verbund \"Verkehrsverbund Rhein-Ruhr (VRR)\" bzw. \"Verkehrs- und Tarifverbund Stuttgart (VVS)\". Toggle aus -> 200 source_status=\"disabled\"; unbekannte Station oder keine Abfahrten -> 200 source_status=\"no_data\"; toter Upstream ohne Cache -> 503 mit Hint.",
        "x-description-en": "Returns the real-time departure board of a station in one of the six VRR core cities (duesseldorf, dortmund, essen, duisburg, bochum, wuppertal, key-less Mentz EFA efa.vrr.de) or in Stuttgart (VVS, key-less Mentz EFA www3.vvs.de): per departure the line, direction, minutes until departure and delay in seconds. The slug selects the city (city-scoping via the AGS5 prefix); a non-EFA slug returns 404. The station query parameter is a station name (default the city's main station), resolved internally via StopFinder to the EFA global id. The meta block additionally carries as_of and refresh_seconds (60). Tier C live-only: the EFA licence is not clearly open (source license = unknown), live data only, kept only briefly in the Redis cache. Attribution per network \"Verkehrsverbund Rhein-Ruhr (VRR)\" or \"Verkehrs- und Tarifverbund Stuttgart (VVS)\". Disabled -> 200 source_status=\"disabled\"; unknown station or no departures -> 200 source_status=\"no_data\"; dead upstream with no cache -> 503 with a hint.",
        "operationId": "getLiveVrrDepartures",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "EFA-Verbund-Stadt-Slug: VRR-Kernstädte (duesseldorf, dortmund, essen, duisburg, bochum, wuppertal) oder VVS Stuttgart (stuttgart). Andere Städte liefern 404.",
            "schema": {
              "type": "string",
              "enum": [
                "duesseldorf",
                "dortmund",
                "essen",
                "duisburg",
                "bochum",
                "wuppertal",
                "stuttgart"
              ]
            }
          },
          {
            "name": "station",
            "in": "query",
            "required": false,
            "description": "Stationsname (Default Hauptbahnhof der Stadt).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Live-Abfahrten-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte (Nicht-EFA-)Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/{city}/transit/departures": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-ÖPNV-Abfahrten je Halt mit Verspätung (GTFS-RT, Tier B)",
        "x-summary-en": "Live public-transit departures per stop with delay (GTFS-RT, Tier B)",
        "description": "Liefert die Live-Abfahrten eines Halts mit aktueller Verspätung aus dem GTFS-RT-Feed (gtfs.de bzw. Mobilithek-DELFI, CC-BY-SA = Tier B) im Live-Envelope (meta mit as_of und refresh_seconds=45, dem Poller-Takt). Der Request-Pfad liest NUR aus Redis: ein Hintergrund-Poller parst den Feed einmal je Takt, sodass nicht bei jedem Request ein 68-MB-Feed geparst wird. RT-only-Semantik: ausgeliefert werden ausschliesslich Trips mit Echtzeit-Update; Fahrplan-Sollzeiten sind NICHT enthalten. Traegt ein Update eine absolute Abfahrtszeit (z.B. Berlin/VBB), wird sie auf Zukunft geprueft (90-s-Grace) und aufsteigend sortiert; Delay-only- Updates ohne absolute Zeit (Regelfall im bundesweiten Feed) kommen MIT delay_s/delay_min und departure_time/minutes_until = null nach den zeitbehafteten Eintraegen. stop_id darf Parent- ODER Steig-Ebene sein (de:AGS:nr bzw. de:AGS:nr:bereich:steig), beide werden aufgeloest. meta weist zusaetzlich raw_updates_total und filtered_out aus (ehrliche Rohzahlen, keine stille Kappung). Toggle aus -> 200 source_status=\"disabled\"; kein Update in Redis oder alles gefiltert -> 200 source_status=\"no_data\"; ungültige stop_id -> 400. Reine Live-Daten, nur im Redis-Cache gehalten (Tier B).",
        "x-description-en": "Returns a stop's live departures with current delay from the GTFS-RT feed (gtfs.de or Mobilithek-DELFI, CC-BY-SA = Tier B) in the live envelope (meta with as_of and refresh_seconds=45, the poller cadence). The request path reads ONLY from Redis: a background poller parses the feed once per cadence, so no 68 MB feed is parsed per request. RT-only semantics: only trips with a realtime update are served; scheduled timetable times are NOT included. When an update carries an absolute departure time (e.g. Berlin/VBB) it is checked for the future (90 s grace) and sorted ascending; delay-only updates without an absolute time (the norm in the nationwide feed) are served WITH delay_s/delay_min and departure_time/minutes_until = null after the timed entries. stop_id may be parent or platform level (de:AGS:nr or de:AGS:nr:area:platform), both resolve. meta additionally exposes raw_updates_total and filtered_out (honest raw counts, no silent truncation). Toggle off -> 200 source_status=\"disabled\"; no update in Redis or everything filtered out -> 200 source_status=\"no_data\"; invalid stop_id -> 400. Live data only, kept in the Redis cache (Tier B).",
        "operationId": "getLiveTransitDepartures",
        "parameters": [
          {
            "name": "city",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "berlin"
            }
          },
          {
            "name": "stop_id",
            "in": "query",
            "required": true,
            "description": "DELFI-Halt-ID im Muster de:<AGS>:<id> (gegen eine Allowlist geprüft). Gültige IDs je Stadt liefert GET /api/v1/cities/{slug}/transit im Feld stop_id. NICHT die trip_stop_id der Bahnhofstafeln, die bezeichnet einen Halt einer Zugfahrt.",
            "x-description-en": "DELFI stop ID in the pattern de:<AGS>:<id> (checked against an allowlist). Valid ids per city come from GET /api/v1/cities/{slug}/transit, field stop_id. NOT the trip_stop_id of the station boards, which denotes one stop of one train run.",
            "schema": {
              "type": "string",
              "example": "de:11000:900003101::6"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Live-Abfahrten-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ungültige oder fehlende stop_id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/{city}/transit/trips/{trip_id}": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-ÖPNV-Fahrt-Detail inkl. geschätzter Position (GTFS-RT, Tier B)",
        "x-summary-en": "Live public-transit trip detail incl. estimated position (GTFS-RT, Tier B)",
        "description": "Liefert das Fahrt-Detail (Verspätung, Halt-Updates) inklusive linear interpolierter Position (als estimated=true gekennzeichnet) aus dem GTFS-RT-Feed (Tier B) im Live-Envelope (refresh_seconds=45). Der Request-Pfad liest NUR aus Redis. Lässt sich die Fahrt nicht gegen das statische GTFS auflösen, trägt der Payload unresolved=true (ehrlich statt 500). Toggle aus -> disabled; kein Update -> no_data. Reine Live-Daten, nur im Redis-Cache gehalten (Tier B).",
        "x-description-en": "Returns the trip detail (delay, stop updates) including a linearly-interpolated position (flagged as estimated=true) from the GTFS-RT feed (Tier B) in the live envelope (refresh_seconds=45). The request path reads ONLY from Redis. If the trip cannot be resolved against the static GTFS, the payload carries unresolved=true (honest instead of 500). Toggle off -> disabled; no update -> no_data. Live data only, kept in the Redis cache (Tier B).",
        "operationId": "getLiveTransitTrip",
        "parameters": [
          {
            "name": "city",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "berlin"
            }
          },
          {
            "name": "trip_id",
            "in": "path",
            "required": true,
            "description": "GTFS-RT-Fahrt-ID. Fluechtig: frische IDs liefert GET /api/v1/live/{city}/transit/departures im Feld trip_id. Eine abgelaufene ID ist kein Fehler, sondern 200 source_status=\"no_data\".",
            "x-description-en": "GTFS-RT trip id. Volatile: fresh ids come from GET /api/v1/live/{city}/transit/departures, field trip_id. An expired id is not an error but 200 source_status=\"no_data\".",
            "schema": {
              "type": "string",
              "example": "300657118"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Live-Fahrt-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ungültige trip_id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/{city}/transit/routes/{route_id}/status": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-ÖPNV-Verspätungslage einer Linie (GTFS-RT, Tier B)",
        "x-summary-en": "Live public-transit delay overview for a route (GTFS-RT, Tier B)",
        "description": "Aggregiert die aktiven Fahrten einer Linie aus dem GTFS-RT-Feed (Tier B): active_trips (Anzahl), avg_delay_s und max_delay_s, im Live-Envelope (refresh_seconds=45). Der Request-Pfad liest NUR aus Redis. Toggle aus -> disabled; keine aktiven Fahrten -> no_data. Reine Live-Daten, nur im Redis-Cache gehalten (Tier B).",
        "x-description-en": "Aggregates a route's active trips from the GTFS-RT feed (Tier B): active_trips (count), avg_delay_s and max_delay_s, in the live envelope (refresh_seconds=45). The request path reads ONLY from Redis. Toggle off -> disabled; no active trips -> no_data. Live data only, kept in the Redis cache (Tier B).",
        "operationId": "getLiveTransitRouteStatus",
        "parameters": [
          {
            "name": "city",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "berlin"
            }
          },
          {
            "name": "route_id",
            "in": "path",
            "required": true,
            "description": "GTFS-RT-Linien-ID. Fluechtig: frische IDs liefert GET /api/v1/live/{city}/transit/departures im Feld route_id. Eine abgelaufene ID ist kein Fehler, sondern 200 source_status=\"no_data\".",
            "x-description-en": "GTFS-RT route id. Volatile: fresh ids come from GET /api/v1/live/{city}/transit/departures, field route_id. An expired id is not an error but 200 source_status=\"no_data\".",
            "schema": {
              "type": "string",
              "example": "17292_700"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Live-Linienstatus-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ungültige route_id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/{slug}/air": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-Alias für Luftqualität (Umweltbundesamt)",
        "x-summary-en": "Live alias for air quality (Federal Environment Agency)",
        "description": "Spiegelt den bestehenden UBA-Luft-Endpunkt unter der Live-Kategorie. Gleicher Envelope-Kontrakt wie /api/v1/cities/{slug}/air, derselbe Handler. Der alte Pfad bleibt als Alias erhalten und ist als veraltet markiert (Deprecation-Header und deprecated=true).",
        "x-description-en": "Mirrors the existing UBA air endpoint under the live category. Same envelope contract as /api/v1/cities/{slug}/air, same handler. The old path remains as an alias and is marked deprecated (Deprecation header and deprecated=true).",
        "operationId": "getLiveAir",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Luft-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/{slug}/air-uba": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-Alias für Luftqualität UBA",
        "x-summary-en": "Live alias for UBA air quality",
        "description": "Spiegelt den bestehenden UBA-Luft-Endpunkt unter der Live-Kategorie. Gleicher Envelope-Kontrakt wie /api/v1/cities/{slug}/air-uba, derselbe Handler. Der alte Pfad bleibt als Alias erhalten und ist als veraltet markiert.",
        "x-description-en": "Mirrors the existing UBA air endpoint under the live category. Same envelope contract as /api/v1/cities/{slug}/air-uba, same handler. The old path remains as an alias and is marked deprecated.",
        "operationId": "getLiveAirUba",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Luft-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/{slug}/water-level": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-Alias für Pegelstand PEGELONLINE",
        "x-summary-en": "Live alias for PEGELONLINE water level",
        "description": "Spiegelt den bestehenden PEGELONLINE-Endpunkt unter der Live-Kategorie. Gleicher Envelope-Kontrakt wie /api/v1/cities/{slug}/water-level, derselbe Handler. Der alte Pfad bleibt als Alias erhalten und ist als veraltet markiert.",
        "x-description-en": "Mirrors the existing PEGELONLINE endpoint under the live category. Same envelope contract as /api/v1/cities/{slug}/water-level, same handler. The old path remains as an alias and is marked deprecated.",
        "operationId": "getLiveWaterLevel",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Pegelstand-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/{slug}/traffic": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-Alias für Verkehr und Baustellen Autobahn",
        "x-summary-en": "Live alias for Autobahn traffic and roadworks",
        "description": "Spiegelt den bestehenden Autobahn-Verkehrs-Endpunkt unter der Live-Kategorie. Gleicher Envelope-Kontrakt wie /api/v1/cities/{slug}/traffic, derselbe Handler. Der alte Pfad bleibt als Alias erhalten und ist als veraltet markiert.",
        "x-description-en": "Mirrors the existing Autobahn traffic endpoint under the live category. Same envelope contract as /api/v1/cities/{slug}/traffic, same handler. The old path remains as an alias and is marked deprecated.",
        "operationId": "getLiveTraffic",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Verkehrs-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/{slug}/webcams": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-Alias für Autobahn-Webcams",
        "x-summary-en": "Live alias for Autobahn webcams",
        "description": "Spiegelt den bestehenden Autobahn-Webcam-Endpunkt unter der Live-Kategorie. Gleicher Envelope-Kontrakt wie /api/v1/cities/{slug}/webcams, derselbe Handler. Der alte Pfad bleibt als Alias erhalten und ist als veraltet markiert.",
        "x-description-en": "Mirrors the existing Autobahn webcam endpoint under the live category. Same envelope contract as /api/v1/cities/{slug}/webcams, same handler. The old path remains as an alias and is marked deprecated.",
        "operationId": "getLiveWebcams",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Webcam-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/live/{slug}/flood": {
      "get": {
        "tags": [
          "Live"
        ],
        "summary": "Live-Alias für Hochwasser-Warnstufen LHP",
        "x-summary-en": "Live alias for LHP flood warning levels",
        "description": "Spiegelt den bestehenden LHP-Hochwasser-Endpunkt unter der Live-Kategorie. Gleicher Envelope-Kontrakt wie /api/v1/cities/{slug}/flood, derselbe Handler. Der alte Pfad bleibt als Alias erhalten und ist als veraltet markiert.",
        "x-description-en": "Mirrors the existing LHP flood endpoint under the live category. Same envelope contract as /api/v1/cities/{slug}/flood, same handler. The old path remains as an alias and is marked deprecated.",
        "operationId": "getLiveFlood",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Hochwasser-Envelope (CanonicalRecord oder null)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CanonicalRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unbekannte Stadt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Quelle vorübergehend nicht erreichbar (kein Cache)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "headers": {
      "ETag": {
        "description": "Stabiler Entity-Tag des Response-Bodys (sha256-basiert). Nur auf erfolgreichen GET-Reads (200) gesetzt, nie auf Fehler-Envelopes/503.",
        "schema": {
          "type": "string"
        }
      },
      "CacheControl": {
        "description": "Cache-Control je Ressource (\"public, max-age=<ttl>, stale-while-revalidate=<ttl>, stale-if-error=<ttl>\"). TTL aus der serverseitigen CACHE_TTL-Map (z.B. wikidata 86400 s, dwd 1800 s, uba 600 s, default 300 s). stale-while-revalidate/stale-if-error erlauben einem Shared Cache (Cloudflare), bei Ablauf bzw. Origin-Fehler kurz die letzte gute Antwort weiterzuliefern. Echtzeit-Endpunkte (/api/v1/live/*) liefern stattdessen \"no-store\".",
        "schema": {
          "type": "string"
        }
      },
      "RateLimitLimit": {
        "description": "Erlaubte Requests im aktuellen Fenster (IETF RateLimit-Header).",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitRemaining": {
        "description": "Verbleibende Requests im aktuellen Fenster.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitReset": {
        "description": "Sekunden bis zum Reset des Fensters.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "responses": {
      "NotModified": {
        "description": "Not Modified. If-None-Match stimmte mit dem aktuellen ETag überein; es wird kein Body geliefert (ETag + Cache-Control bleiben erhalten).",
        "headers": {
          "ETag": {
            "$ref": "#/components/headers/ETag"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          }
        }
      }
    },
    "schemas": {
      "Meta": {
        "type": "object",
        "properties": {
          "correlation_id": {
            "type": "string",
            "nullable": true
          },
          "source_status": {
            "type": "string",
            "description": "Ehrlicher Quellen-Status der Antwort. \"ok\" (Daten vorhanden), \"no_data\" (Quelle erreichbar/abgedeckt, aber gerade keine Daten), \"disabled\" (Quelle per Toggle aus), \"not_ingested\" (kein Snapshot), \"not_covered\" (Stadt ist für diesen teilabgedeckten Endpunkt strukturell nicht abgedeckt; data=null, siehe covered_cities). Klar unterscheidbar vom 404 (Stadt unbekannt).",
            "enum": [
              "ok",
              "no_data",
              "disabled",
              "not_ingested",
              "not_covered"
            ]
          },
          "cache_status": {
            "type": "string",
            "nullable": true,
            "description": "Cache-Herkunft (hit/miss/stale), sofern die Route cacht."
          },
          "covered_cities": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Nur bei source_status=\"not_covered\": die Stadt-Slugs, die dieser teilabgedeckte Endpunkt tatsächlich bedient (z. B. flood, webcams, traffic, road-events)."
          },
          "pagination": {
            "type": "object",
            "description": "Bei paginierbaren Datenart-Listen (charging, energy, events, transit, OSM-Feature-Endpunkte): der EHRLICH ausgewiesene Ausschnitt (keine stille Kappung). KANAL-ABHÄNGIGER Default für gleiche URL: direktes REST liefert die volle Liste (limit=null, returned==total, truncated=false); GPT-Actions (erkannt am OpenAI-Header) bekommen ein serverseitig gebundenes Default-Limit; MCP-Aufrufe sind gebunden, weil der MCP-Client selbst ein Default-Limit setzt. Kanalunabhängige Overrides: limit=all (oder ?all=1) erzwingt die volle Liste, limit + offset blättern gezielt. meta.pagination ist bei ALLEN Kanälen gesetzt.",
            "x-description-en": "For paginable data-type lists (charging, energy, events, transit, OSM feature endpoints): the honestly reported slice (no silent capping). CHANNEL-DEPENDENT default for the same URL: direct REST returns the full list (limit=null, returned==total, truncated=false); GPT Actions (detected via the OpenAI header) get a server-side bound default limit; MCP calls are bound because the MCP client sets a default limit itself. Channel-independent overrides: limit=all (or ?all=1) forces the full list, limit + offset page explicitly. meta.pagination is set on ALL channels.",
            "properties": {
              "total": {
                "type": "integer",
                "description": "Gesamtzahl der Einträge (voller Bestand)."
              },
              "returned": {
                "type": "integer",
                "description": "Zahl der auf dieser Seite ausgelieferten Einträge."
              },
              "limit": {
                "type": "integer",
                "nullable": true,
                "description": "Angewandtes Seiten-Limit; null bei Vollausgabe (REST-Default oder limit=all)."
              },
              "offset": {
                "type": "integer",
                "description": "Start-Offset der Seite."
              },
              "truncated": {
                "type": "boolean",
                "description": "true, wenn hinter dieser Seite noch Einträge liegen (offset + returned < total); bei Vollausgabe false."
              }
            }
          }
        }
      },
      "Health": {
        "type": "object",
        "required": [
          "status",
          "version",
          "redis"
        ],
        "properties": {
          "status": {
            "type": "string",
            "example": "ok"
          },
          "version": {
            "type": "string",
            "example": "1.0.0"
          },
          "redis": {
            "type": "boolean",
            "description": "true wenn Redis erreichbar (Ping erfolgreich)"
          }
        }
      },
      "Ping": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "pong"
            ],
            "properties": {
              "pong": {
                "type": "boolean",
                "example": true
              }
            }
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      },
      "SourceStatus": {
        "type": "object",
        "required": [
          "source",
          "enabled",
          "breaker_state"
        ],
        "properties": {
          "source": {
            "type": "string",
            "example": "wikidata"
          },
          "enabled": {
            "type": "boolean"
          },
          "breaker_state": {
            "type": "string",
            "enum": [
              "CLOSED",
              "OPEN",
              "HALF_OPEN"
            ]
          }
        }
      },
      "Attribution": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "example": "Wikidata"
          },
          "license_url": {
            "type": "string",
            "nullable": true
          },
          "modified": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "CityBaseDataPayload": {
        "type": "object",
        "required": [
          "kind"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "city_base"
            ],
            "default": "city_base"
          },
          "population": {
            "type": "integer",
            "nullable": true
          },
          "area_km2": {
            "type": "number",
            "nullable": true
          }
        }
      },
      "AirQualityPayload": {
        "type": "object",
        "required": [
          "kind"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "air_quality"
            ],
            "default": "air_quality"
          },
          "station_id": {
            "type": "string",
            "nullable": true
          },
          "pm10": {
            "type": "number",
            "nullable": true
          },
          "no2": {
            "type": "number",
            "nullable": true
          },
          "pm25": {
            "type": "number",
            "nullable": true
          },
          "o3": {
            "type": "number",
            "nullable": true
          },
          "so2": {
            "type": "number",
            "nullable": true
          }
        }
      },
      "WeatherPayload": {
        "type": "object",
        "required": [
          "kind"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "weather"
            ],
            "default": "weather"
          },
          "station_id": {
            "type": "string",
            "nullable": true
          },
          "temperature_c": {
            "type": "number",
            "nullable": true
          },
          "humidity": {
            "type": "number",
            "nullable": true
          },
          "wind_speed": {
            "type": "number",
            "nullable": true
          },
          "condition": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "PoiPayload": {
        "type": "object",
        "required": [
          "kind",
          "poi_type",
          "count"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "poi"
            ],
            "default": "poi"
          },
          "poi_type": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "total_available": {
            "type": "integer",
            "nullable": true
          },
          "truncated": {
            "type": "boolean",
            "default": false
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "TrafficEventPayload": {
        "type": "object",
        "required": [
          "kind"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "traffic_event"
            ],
            "default": "traffic_event"
          },
          "station_id": {
            "type": "string",
            "nullable": true
          },
          "roadworks": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "RoadEventPayload": {
        "type": "object",
        "required": [
          "kind"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "road_event"
            ],
            "default": "road_event"
          },
          "city_source": {
            "type": "string",
            "nullable": true
          },
          "events": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "WebcamPayload": {
        "type": "object",
        "required": [
          "kind",
          "count"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "webcam"
            ],
            "default": "webcam"
          },
          "count": {
            "type": "integer"
          },
          "webcams": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "TransitStopPayload": {
        "type": "object",
        "required": [
          "kind",
          "stop_id",
          "stop_name"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "transit_stop"
            ],
            "default": "transit_stop"
          },
          "stop_id": {
            "type": "string"
          },
          "stop_name": {
            "type": "string"
          },
          "location_type": {
            "type": "integer",
            "nullable": true
          },
          "parent_station": {
            "type": "string",
            "nullable": true
          },
          "platform_code": {
            "type": "string",
            "nullable": true
          },
          "wheelchair_boarding": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "ChargingStationPayload": {
        "type": "object",
        "required": [
          "kind",
          "count"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "charging_station"
            ],
            "default": "charging_station"
          },
          "count": {
            "type": "integer"
          },
          "stations": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "WaterLevelPayload": {
        "type": "object",
        "required": [
          "kind"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "water_level"
            ],
            "default": "water_level"
          },
          "station": {
            "type": "string",
            "nullable": true
          },
          "water": {
            "type": "string",
            "nullable": true
          },
          "value": {
            "type": "number",
            "nullable": true
          },
          "unit": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "FloodWarningPayload": {
        "type": "object",
        "required": [
          "kind"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "flood_warning"
            ],
            "default": "flood_warning"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "stand": {
            "type": "string",
            "nullable": true,
            "deprecated": true,
            "description": "Abgekündigt, siehe as_of (identischer Wert). Wird frühestens 30 Tage nach Ankündigung vom 2026-08-01 entfernt.",
            "x-description-en": "Deprecated, see as_of (identical value). Will be removed no earlier than 30 days after the announcement of 2026-08-01."
          },
          "as_of": {
            "type": "string",
            "nullable": true,
            "description": "Stand-Zeitstempel-Text der LHP-Antwort (kanonischer Name; ersetzt das abgekündigte stand).",
            "x-description-en": "As-of timestamp text of the LHP response (canonical name; replaces the deprecated stand)."
          }
        }
      },
      "PollenUvPayload": {
        "type": "object",
        "required": [
          "kind"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "pollen_uv"
            ],
            "default": "pollen_uv"
          },
          "region_id": {
            "type": "integer",
            "nullable": true
          },
          "region_name": {
            "type": "string",
            "nullable": true
          },
          "pollen": {
            "type": "object"
          },
          "uv_index": {
            "type": "number",
            "nullable": true
          }
        }
      },
      "DemographicsPayload": {
        "type": "object",
        "required": [
          "kind"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "demographics"
            ],
            "default": "demographics"
          },
          "population": {
            "type": "integer",
            "nullable": true
          },
          "households": {
            "type": "integer",
            "nullable": true
          },
          "buildings": {
            "type": "integer",
            "nullable": true
          },
          "rent_avg": {
            "type": "number",
            "nullable": true
          },
          "reference_year": {
            "type": "integer",
            "nullable": true
          },
          "series": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "EnergyAssetPayload": {
        "type": "object",
        "required": [
          "kind",
          "count"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "energy_asset"
            ],
            "default": "energy_asset"
          },
          "count": {
            "type": "integer"
          },
          "by_type": {
            "type": "object"
          },
          "assets": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "AdminBoundaryPayload": {
        "type": "object",
        "required": [
          "kind"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "admin_boundary"
            ],
            "default": "admin_boundary"
          },
          "ags": {
            "type": "string",
            "nullable": true
          },
          "gen_name": {
            "type": "string",
            "nullable": true
          },
          "area_km2": {
            "type": "number",
            "nullable": true
          },
          "reference_year": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "ElectionResultPayload": {
        "type": "object",
        "required": [
          "kind"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "election_result"
            ],
            "default": "election_result"
          },
          "election": {
            "type": "string",
            "nullable": true
          },
          "granularity": {
            "type": "string",
            "default": "teilweise",
            "deprecated": true,
            "description": "Abgekündigt, siehe coverage_granularity (gleiche Bedeutung, deutsche Werte \"stadt\"/\"teilweise\"). Wird frühestens 30 Tage nach Ankündigung vom 2026-08-01 entfernt.",
            "x-description-en": "Deprecated, see coverage_granularity (same meaning, German values \"stadt\"/\"teilweise\"). Will be removed no earlier than 30 days after the announcement of 2026-08-01."
          },
          "coverage_granularity": {
            "type": "string",
            "nullable": true,
            "description": "Abdeckungs-Granularität (kanonischer Name): \"city\" = stadtgenau aus den Wahlkreisen der Stadt aggregiert, \"partial\" = Wahlkreis/Kreis- Ebene, Stadt nur teilweise. Ersetzt das abgekündigte granularity.",
            "x-description-en": "Coverage granularity (canonical name): \"city\" = city-exact, aggregated from the city's constituencies, \"partial\" = constituency/district level, city only partially covered. Replaces the deprecated granularity."
          },
          "area_name": {
            "type": "string",
            "nullable": true
          },
          "turnout": {
            "type": "string",
            "nullable": true,
            "description": "Wahlbeteiligung in Prozent als String mit Dezimal-Komma (kerg2, Zeile Gruppenname \"Wählende\", Spalte Prozent), z.B. \"82,5122\"."
          },
          "results": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "HolidayPayload": {
        "type": "object",
        "required": [
          "kind"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "holiday"
            ],
            "default": "holiday"
          },
          "state": {
            "type": "string",
            "nullable": true
          },
          "year": {
            "type": "integer",
            "nullable": true
          },
          "holidays": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "school_holidays": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "HospitalPayload": {
        "type": "object",
        "required": [
          "kind",
          "count"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "hospital"
            ],
            "default": "hospital"
          },
          "count": {
            "type": "integer"
          },
          "hospitals": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "reference_date": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "CanonicalRecord": {
        "type": "object",
        "required": [
          "city_slug",
          "retrieved_at",
          "source",
          "license_id",
          "license_tier",
          "schema_version",
          "record_id",
          "content_hash",
          "attribution",
          "payload"
        ],
        "properties": {
          "city_slug": {
            "type": "string"
          },
          "schema_version": {
            "type": "integer",
            "default": 1
          },
          "record_id": {
            "type": "string",
            "description": "Deterministischer Primärschlüssel (computed, immer im Dump)."
          },
          "content_hash": {
            "type": "string",
            "description": "Deterministischer Dedup-Hash der Nutzdaten (computed, immer im Dump)."
          },
          "ags": {
            "type": "string",
            "nullable": true,
            "description": "Amtlicher Gemeindeschlüssel als Join-Key (kein Pfadsegment)."
          },
          "wikidata_qid": {
            "type": "string",
            "nullable": true,
            "description": "Wikidata-QID als Join-Key."
          },
          "geo": {
            "nullable": true,
            "type": "object",
            "properties": {
              "lat": {
                "type": "number"
              },
              "lon": {
                "type": "number"
              }
            }
          },
          "observed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "retrieved_at": {
            "type": "string",
            "format": "date-time"
          },
          "source": {
            "type": "string",
            "description": "Kanonischer Quellname (SourceId). Seit 2026-08-01 tragen alle Quellen einheitlich englische Namen; historische Antworten und Bestandsdaten konnten davor alte deutsche Quellnamen tragen (z.B. hamburg_baustellen statt hamburg_roadworks). Die vollständige Zuordnungstabelle alt -> neu steht im Changelog (Eintrag 2026-08-01).",
            "x-description-en": "Canonical source name (SourceId). Since 2026-08-01 all sources carry uniform English names; historical responses and stored records may still carry the former German source names (e.g. hamburg_baustellen instead of hamburg_roadworks). The full old -> new mapping table is in the changelog (entry 2026-08-01).",
            "enum": [
              "wikidata",
              "dwd",
              "osm",
              "autobahn",
              "bnetza",
              "uba",
              "delfi",
              "hvv",
              "pegelonline",
              "lhp",
              "dwd_pollen",
              "dwd_fire",
              "eea_bathing",
              "klinik_atlas",
              "db_fasta",
              "genesis",
              "zensus",
              "mastr",
              "smard",
              "dwd_warnings",
              "bkg",
              "bundeswahl",
              "holidays",
              "berlin_viz",
              "hamburg_roadworks",
              "koeln_road_events",
              "muenchen_roadworks",
              "dortmund_roadworks",
              "rostock_roadworks",
              "sperrinfosys",
              "mobidata_bw",
              "destination_one",
              "koeln_events",
              "koeln_traffic_flow",
              "koeln_roadworks_live",
              "koeln_incidents_live",
              "koeln_lez_live",
              "berlin_traffic_reports",
              "dortmund_parking",
              "kiel_counting_stations",
              "eround_charging",
              "frankfurt_parking",
              "wuppertal_parking",
              "magdeburg_parking",
              "koeln_parking",
              "gtfs_rt",
              "hvv_geofox",
              "vgn",
              "rmv",
              "vrr",
              "vvs",
              "hamburg_traffic_situation",
              "kba",
              "unfallatlas",
              "tankerkoenig",
              "bremen_roadworks",
              "hannover_traffic_reports",
              "gbfs",
              "inkar",
              "wegweiser",
              "bka_pks",
              "db_timetables",
              "boris",
              "stada",
              "regionalstatistik",
              "solar",
              "solar_cadastre",
              "muenchen_parking",
              "muenchen_bike_counts",
              "muenchen_parking_onstreet",
              "muenchen_park_and_ride",
              "muenchen_mobility_points",
              "muenchen_bike_parking",
              "leipzig_bike_counts",
              "hamburg_bike_counts",
              "berlin_bike_counts",
              "stuttgart_bike_counts",
              "koeln_bike_counts",
              "essen_bike_counts",
              "duesseldorf_bike_counts",
              "parkendd",
              "heritage",
              "tree_cadastre",
              "zensus_grid",
              "oeffentlichevergabe",
              "district_heating",
              "koeln_wait_times",
              "bbk_nina",
              "council",
              "mobidata_parkapi",
              "pr_hessen_parking",
              "db_bahnpark",
              "muenster_parking",
              "aachen_parking",
              "oldenburg_parking",
              "kaiserslautern_parking",
              "dresden_parking",
              "karlsruhe_parking",
              "hamburg_parking"
            ]
          },
          "license_id": {
            "type": "string",
            "enum": [
              "cc0",
              "cc_by_4_0",
              "dl_de_by_2_0",
              "dl_de_zero_2_0",
              "geonutzv",
              "odbl",
              "cc_by_sa_4_0",
              "unknown"
            ]
          },
          "license_tier": {
            "type": "string",
            "enum": [
              "A",
              "B",
              "C"
            ]
          },
          "attribution": {
            "$ref": "#/components/schemas/Attribution"
          },
          "payload": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CityBaseDataPayload"
              },
              {
                "$ref": "#/components/schemas/AirQualityPayload"
              },
              {
                "$ref": "#/components/schemas/WeatherPayload"
              },
              {
                "$ref": "#/components/schemas/PoiPayload"
              },
              {
                "$ref": "#/components/schemas/TrafficEventPayload"
              },
              {
                "$ref": "#/components/schemas/TransitStopPayload"
              },
              {
                "$ref": "#/components/schemas/ChargingStationPayload"
              },
              {
                "$ref": "#/components/schemas/WaterLevelPayload"
              },
              {
                "$ref": "#/components/schemas/FloodWarningPayload"
              },
              {
                "$ref": "#/components/schemas/PollenUvPayload"
              },
              {
                "$ref": "#/components/schemas/DemographicsPayload"
              },
              {
                "$ref": "#/components/schemas/EnergyAssetPayload"
              },
              {
                "$ref": "#/components/schemas/AdminBoundaryPayload"
              },
              {
                "$ref": "#/components/schemas/ElectionResultPayload"
              },
              {
                "$ref": "#/components/schemas/HolidayPayload"
              },
              {
                "$ref": "#/components/schemas/HospitalPayload"
              },
              {
                "$ref": "#/components/schemas/RoadEventPayload"
              },
              {
                "$ref": "#/components/schemas/WebcamPayload"
              }
            ],
            "discriminator": {
              "propertyName": "kind"
            }
          }
        }
      },
      "ErrorDetail": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "example": "upstream_unavailable"
          },
          "message": {
            "type": "string",
            "example": "Simulated upstream failure"
          },
          "hint": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "error",
          "meta"
        ],
        "properties": {
          "error": {
            "$ref": "#/components/schemas/ErrorDetail"
          },
          "meta": {
            "type": "object",
            "properties": {
              "correlation_id": {
                "type": "string",
                "nullable": true
              },
              "generated_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      }
    }
  }
}