{
  "openapi": "3.1.0",
  "info": {
    "title": "wetter381.de Umweltmessdaten-API",
    "version": "1.4.0",
    "description": "Öffentliche JSON-API für private Feinstaub-, Wetter- und Schallmessungen aus Braunschweig-Heidberg. Die Werte sind keine amtlichen Messdaten.",
    "contact": {
      "name": "wetter381.de",
      "url": "https://wetter381.de/kontakt.php"
    },
    "license": {
      "name": "Weiterverwendung mit Quellenangabe",
      "url": "https://wetter381.de/api-doku.php#weiterverwendung"
    }
  },
  "servers": [
    {
      "url": "https://wetter381.de"
    }
  ],
  "externalDocs": {
    "description": "API-Dokumentation",
    "url": "https://wetter381.de/api-doku.php"
  },
  "paths": {
    "/api/current.php": {
      "get": {
        "operationId": "getCurrentMeasurements",
        "summary": "Aktuelle Messwerte",
        "description": "Liefert die letzten bekannten Werte beider Messstationen.",
        "responses": {
          "200": {
            "description": "Aktuelle Messwerte",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CurrentResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimitExceeded"
          }
        }
      }
    },
    "/api/status.php": {
      "get": {
        "operationId": "getMeasurementStatus",
        "summary": "Status und Datenalter",
        "description": "Liefert Stationen, letzte Messzeitpunkte und das Alter der Daten.",
        "responses": {
          "200": {
            "description": "Aktueller Status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StatusResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimitExceeded"
          }
        }
      }
    },
    "/api/history.php": {
      "get": {
        "operationId": "getMeasurementHistory",
        "summary": "Messwertverlauf",
        "description": "Liefert Messpunkte aus einem wählbaren Zeitraum. sample_interval wählt je Sensor den letzten vorhandenen Punkt eines Zeitfensters aus; es werden keine Mittelwerte berechnet. Antworten enthalten höchstens 2.000 gleichmäßig ausgewählte Punkte.",
        "parameters": [
          {
            "name": "hours",
            "in": "query",
            "description": "Zeitraum in Stunden, mindestens 0.5.",
            "schema": {
              "type": "number",
              "minimum": 0.5,
              "default": 24,
              "examples": [0.5, 24, 168, 720]
            }
          },
          {
            "name": "sample_interval",
            "in": "query",
            "description": "Auswahlintervall in Minuten. Je Sensor bleibt der letzte vorhandene Messpunkt des Zeitfensters erhalten. Es wird kein Mittelwert berechnet.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0,
              "examples": [0, 15, 60]
            }
          },
          {
            "name": "bucket",
            "in": "query",
            "deprecated": true,
            "description": "Veralteter Alias für sample_interval.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0,
              "examples": [0, 15, 60]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Messwertverlauf",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoryResponse"
                }
              }
            }
          },
          "500": {
            "description": "Verlauf derzeit nicht verfügbar"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitExceeded"
          }
        }
      }
    },
    "/api/spectrum.php": {
      "get": {
        "operationId": "getNoiseSpectrumHistory",
        "summary": "DNMS-Terzspektrum",
        "description": "Liefert Z-bewertete Terzbandpegel von 20 Hz bis 20 kHz. sample_interval wählt den letzten vorhandenen Spektralpunkt eines Zeitfensters aus; es werden keine Mittelwerte berechnet. Antworten enthalten höchstens 1.000 Punkte.",
        "parameters": [
          {
            "name": "hours",
            "in": "query",
            "description": "Zeitraum in Stunden zwischen 0.5 und 720.",
            "schema": { "type": "number", "minimum": 0.5, "maximum": 720, "default": 24 }
          },
          {
            "name": "sample_interval",
            "in": "query",
            "description": "Auswahlintervall in Minuten. Pro Zeitfenster bleibt der letzte vorhandene Spektralpunkt erhalten.",
            "schema": { "type": "integer", "minimum": 0, "maximum": 1440, "default": 0 }
          }
        ],
        "responses": {
          "200": {
            "description": "Terzspektrum",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SpectrumResponse" }
              }
            }
          },
          "500": { "description": "Spektrum derzeit nicht verfügbar" },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      }
    },
    "/api/live.php": {
      "get": {
        "operationId": "streamCurrentNoiseSpectrum",
        "summary": "DNMS-Livestream",
        "description": "Sendet das aktuelle 5-Sekunden-Intervall als Server-Sent Events. Mit once=1 wird ein einzelner JSON-Datensatz zurückgegeben.",
        "parameters": [
          {
            "name": "once",
            "in": "query",
            "description": "Bei 1 wird nur der aktuelle Datensatz als JSON ausgegeben.",
            "schema": { "type": "integer", "enum": [1] }
          }
        ],
        "responses": {
          "200": {
            "description": "Livestream oder aktueller Datensatz",
            "content": {
              "text/event-stream": {
                "schema": { "type": "string" }
              },
              "application/json": {
                "schema": { "$ref": "#/components/schemas/LiveSpectrumPayload" }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" },
          "503": { "description": "Noch keine Detailmessung verfügbar" }
        }
      }
    }
  },
  "components": {
    "responses": {
      "RateLimitExceeded": {
        "description": "Anfragelimit überschritten. Retry-After nennt die Wartezeit in Sekunden.",
        "headers": {
          "Retry-After": {
            "schema": { "type": "integer", "minimum": 1 }
          },
          "X-RateLimit-Limit": {
            "schema": { "type": "integer", "minimum": 1 }
          },
          "X-RateLimit-Remaining": {
            "schema": { "type": "integer", "minimum": 0 }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": ["ok", "error", "retry_after_seconds"],
              "properties": {
                "ok": { "type": "boolean", "const": false },
                "error": { "type": "string", "const": "rate_limit_exceeded" },
                "retry_after_seconds": { "type": "integer", "minimum": 1 }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Station": {
        "type": "object",
        "required": ["name", "area"],
        "properties": {
          "name": { "type": "string", "examples": ["wetter381.de"] },
          "area": { "type": "string", "examples": ["Heidberg, Braunschweig"] }
        }
      },
      "Meta": {
        "type": "object",
        "required": ["generated_at", "license", "timezone", "pressure_reduction_elevation_m", "note", "fields"],
        "properties": {
          "generated_at": { "type": "string", "format": "date-time" },
          "license": { "type": "string" },
          "license_url": { "type": "string", "format": "uri" },
          "documentation_url": { "type": "string", "format": "uri" },
          "openapi_url": { "type": "string", "format": "uri" },
          "timezone": { "type": "string", "const": "Europe/Berlin" },
          "pressure_reduction_elevation_m": { "type": "number", "description": "Für die Luftdruckreduktion konfigurierte Sensorhöhe über NHN in Metern." },
          "note": { "type": "string" },
          "data_age_seconds": { "type": ["integer", "null"], "minimum": 0 },
          "sensor_count": { "type": "integer", "minimum": 0 },
          "fields": {
            "type": "object",
            "additionalProperties": { "type": "string" }
          }
        }
      },
      "Measurements": {
        "type": "object",
        "description": "Messwerte. Nicht jede Station liefert alle Felder.",
        "properties": {
          "pm25": { "type": ["number", "null"], "description": "PM2.5 in µg/m³" },
          "pm10": { "type": ["number", "null"], "description": "PM10 in µg/m³" },
          "temperature": { "type": ["number", "null"], "description": "Temperatur in °C" },
          "humidity": { "type": ["number", "null"], "description": "Relative Luftfeuchte in %" },
          "pressure": { "type": ["number", "null"], "description": "Stationsdruck in hPa, nicht auf Meereshöhe umgerechnet" },
          "pressure_sea_level": { "type": ["number", "null"], "description": "Mit Druck, Temperatur und der konfigurierten Sensorhöhe auf Meereshöhe reduzierter Luftdruck in hPa; vorhandene Feuchtewerte fließen ebenfalls ein" },
          "noise": { "type": ["number", "null"], "description": "LAeq in dB" },
          "noise_min": { "type": ["number", "null"], "description": "LAmin in dB" },
          "noise_max": { "type": ["number", "null"], "description": "LAmax in dB" },
          "noise_z": { "type": ["number", "null"], "description": "LZeq in dB" },
          "noise_z_min": { "type": ["number", "null"], "description": "LZmin in dB" },
          "noise_z_max": { "type": ["number", "null"], "description": "LZmax in dB" }
        },
        "additionalProperties": false
      },
      "SensorReading": {
        "type": "object",
        "required": ["sensor_key", "sensor_name", "kind", "received_at", "summary"],
        "properties": {
          "sensor_key": { "type": "string" },
          "sensor_name": { "type": "string" },
          "kind": { "type": "string", "enum": ["air", "noise", "weather"] },
          "received_at": { "type": ["string", "null"], "format": "date-time" },
          "received_at_label": { "type": ["string", "null"] },
          "data_age_seconds": { "type": ["integer", "null"], "minimum": 0 },
          "summary": { "$ref": "#/components/schemas/Measurements" }
        }
      },
      "HistoryPoint": {
        "type": "object",
        "required": ["time", "sensor_key", "sensor_name", "kind", "summary"],
        "properties": {
          "time": { "type": "string", "format": "date-time" },
          "sensor_key": { "type": "string" },
          "sensor_name": { "type": "string" },
          "kind": { "type": "string", "enum": ["air", "noise", "weather"] },
          "summary": { "$ref": "#/components/schemas/Measurements" }
        }
      },
      "CurrentResponse": {
        "type": "object",
        "required": ["ok", "updated_at", "station", "meta", "sensors"],
        "properties": {
          "ok": { "type": "boolean", "const": true },
          "updated_at": { "type": "string", "format": "date-time" },
          "station": { "$ref": "#/components/schemas/Station" },
          "meta": { "$ref": "#/components/schemas/Meta" },
          "sensors": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/SensorReading" }
          }
        }
      },
      "StatusResponse": {
        "type": "object",
        "required": ["ok", "station", "meta", "sensors"],
        "properties": {
          "ok": { "type": "boolean", "const": true },
          "station": { "$ref": "#/components/schemas/Station" },
          "meta": { "$ref": "#/components/schemas/Meta" },
          "sensors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "sensor_key": { "type": "string" },
                "sensor_name": { "type": "string" },
                "kind": { "type": "string" },
                "received_at": { "type": ["string", "null"], "format": "date-time" },
                "data_age_seconds": { "type": ["integer", "null"], "minimum": 0 }
              }
            }
          }
        }
      },
      "HistoryResponse": {
        "type": "object",
        "required": ["ok", "hours", "sample_interval_minutes", "bucket_minutes", "selection_method", "maximum_points", "station", "meta", "points"],
        "properties": {
          "ok": { "type": "boolean", "const": true },
          "hours": { "type": "number", "minimum": 0.5 },
          "sample_interval_minutes": { "type": ["integer", "null"], "minimum": 0, "description": "Auswahlintervall in Minuten; null bedeutet kein ausdrücklich gewähltes Intervall." },
          "bucket_minutes": { "type": ["integer", "null"], "minimum": 0, "deprecated": true, "description": "Veraltetes Gegenstück zu sample_interval_minutes." },
          "selection_method": { "type": "string", "enum": ["last_point_per_sensor_interval", "raw_points_up_to_limit"] },
          "maximum_points": { "type": "integer", "const": 2000 },
          "station": { "$ref": "#/components/schemas/Station" },
          "meta": { "$ref": "#/components/schemas/Meta" },
          "points": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/HistoryPoint" }
          }
        }
      },
      "SpectrumPoint": {
        "type": "object",
        "required": ["time", "interval", "spectrum_z"],
        "properties": {
          "time": { "type": "string", "format": "date-time" },
          "interval": {
            "type": "string",
            "enum": ["primary", "detail"],
            "description": "Reguläres Messintervall oder ereignisgesteuertes Detailintervall."
          },
          "spectrum_z": {
            "type": "object",
            "description": "Z-bewertete Terzbandpegel. Die Schlüssel sind Mittenfrequenzen in Hz, die Werte Pegel in dB.",
            "additionalProperties": { "type": "number", "minimum": -100, "maximum": 180 }
          }
        }
      },
      "SpectrumResponse": {
        "type": "object",
        "required": ["ok", "hours", "sample_interval_minutes", "selection_method", "maximum_points", "station", "meta", "points"],
        "properties": {
          "ok": { "type": "boolean", "const": true },
          "hours": { "type": "number", "minimum": 0.5, "maximum": 720 },
          "sample_interval_minutes": { "type": ["integer", "null"], "minimum": 0 },
          "selection_method": { "type": "string", "enum": ["last_point_per_interval", "raw_points_up_to_limit"] },
          "maximum_points": { "type": "integer", "const": 1000 },
          "station": { "$ref": "#/components/schemas/Station" },
          "meta": {
            "allOf": [
              { "$ref": "#/components/schemas/Meta" },
              {
                "type": "object",
                "required": ["weighting", "unit", "frequencies_hz"],
                "properties": {
                  "weighting": { "type": "string", "const": "Z" },
                  "unit": { "type": "string", "const": "dB" },
                  "frequencies_hz": {
                    "type": "array",
                    "minItems": 31,
                    "maxItems": 31,
                    "items": { "type": "number" }
                  }
                }
              }
            ]
          },
          "points": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/SpectrumPoint" }
          }
        }
      },
      "LiveSpectrumPayload": {
        "type": "object",
        "required": ["ok", "time", "interval", "summary", "spectrum_z"],
        "properties": {
          "ok": { "type": "boolean", "const": true },
          "time": { "type": "string", "format": "date-time" },
          "interval": { "type": "string", "const": "detail" },
          "summary": { "$ref": "#/components/schemas/Measurements" },
          "spectrum_z": {
            "type": "object",
            "description": "31 Z-bewertete Terzbandpegel von 20 Hz bis 20 kHz.",
            "additionalProperties": { "type": "number", "minimum": -100, "maximum": 180 }
          }
        }
      }
    }
  }
}
