{
  "openapi": "3.1.0",
  "info": {
    "title": "Wahlergebnisse Niedersachsen",
    "version": "1.0.0",
    "description": "Kommunalwahlergebnisse der niedersächsischen Landkreise und kreisfreien Städte, aufbereitet aus den amtlichen Wahlpräsentationen (votemanager). Jede Zahl trägt einen Namen, jede Ebene hat dasselbe Format, alles gibt es auch als CSV. Der Kreis ist das erste Segment jedes Pfades. Nur Lesezugriffe, keine Anmeldung.",
    "contact": {
      "url": "https://github.com/levino/wahlergebnisse/issues"
    },
    "license": {
      "name": "Amtliche Ergebnisse der Wahlleitungen; Geodaten siehe /api/v1/",
      "url": "https://wahlergebnisse.levinkeller.de/api/v1/"
    }
  },
  "servers": [
    {
      "url": "https://wahlergebnisse.levinkeller.de/api/v1"
    }
  ],
  "paths": {
    "/": {
      "get": {
        "summary": "Einstieg mit allen Pfaden und Lizenzen",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/termine": {
      "get": {
        "summary": "Verfügbare Wahltermine",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/kreise": {
      "get": {
        "summary": "Landkreise und kreisfreie Städte mit ihren Wahlleitungen",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/{kreis}": {
      "get": {
        "summary": "Ein Kreis mit seinen Wahlleitungen",
        "parameters": [
          {
            "$ref": "#/components/schemas/KreisParam"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Unbekannter Kreis"
          }
        }
      }
    },
    "/{kreis}/{termin}": {
      "get": {
        "summary": "Überblick: Stand und Auszählfortschritt",
        "parameters": [
          {
            "$ref": "#/components/schemas/KreisParam"
          },
          {
            "$ref": "#/components/schemas/TerminParam"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Unbekannter Termin"
          }
        }
      }
    },
    "/{kreis}/{termin}/behoerden": {
      "get": {
        "summary": "Wahlleitungen mit ihren Wahlen",
        "parameters": [
          {
            "$ref": "#/components/schemas/KreisParam"
          },
          {
            "$ref": "#/components/schemas/TerminParam"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/{kreis}/{termin}/wahlen": {
      "get": {
        "summary": "Alle Wahlen des Termins",
        "parameters": [
          {
            "$ref": "#/components/schemas/KreisParam"
          },
          {
            "$ref": "#/components/schemas/TerminParam"
          },
          {
            "name": "behoerde",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Slug oder AGS"
          },
          {
            "name": "typ",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "landrat",
                "landrat-stichwahl",
                "kreistag",
                "buergermeister",
                "buergermeister-stichwahl",
                "rat",
                "ortsrat",
                "sonstige"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/{kreis}/{termin}/{behoerde}/{wahl}": {
      "get": {
        "summary": "Eine Wahl mit Gesamtergebnis",
        "parameters": [
          {
            "$ref": "#/components/schemas/KreisParam"
          },
          {
            "$ref": "#/components/schemas/TerminParam"
          },
          {
            "$ref": "#/components/schemas/BehoerdeParam"
          },
          {
            "$ref": "#/components/schemas/WahlParam"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Wahl"
                }
              }
            }
          },
          "404": {
            "description": "Unbekannt"
          }
        }
      }
    },
    "/{kreis}/{termin}/{behoerde}/{wahl}/gebiete": {
      "get": {
        "summary": "Alle Gebiete der Wahl mit Ergebnis (JSON oder CSV)",
        "description": "Mit format=csv kommt eine flache Tabelle: eine Zeile je Gebiet und Partei, Semikolon-getrennt, UTF-8 mit BOM. Die Bewerberinnen und Bewerber (kandidaten) stehen nur in der JSON-Fassung.",
        "parameters": [
          {
            "$ref": "#/components/schemas/KreisParam"
          },
          {
            "$ref": "#/components/schemas/TerminParam"
          },
          {
            "$ref": "#/components/schemas/BehoerdeParam"
          },
          {
            "$ref": "#/components/schemas/WahlParam"
          },
          {
            "name": "ebene",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "kreis",
                "gemeinde",
                "wahlbereich",
                "ortsteil",
                "wahlbezirk"
              ]
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv"
              ],
              "default": "json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/{kreis}/{termin}/{behoerde}/{wahl}/gebiete/{gebiet}": {
      "get": {
        "summary": "Ein einzelnes Gebiet",
        "parameters": [
          {
            "$ref": "#/components/schemas/KreisParam"
          },
          {
            "$ref": "#/components/schemas/TerminParam"
          },
          {
            "$ref": "#/components/schemas/BehoerdeParam"
          },
          {
            "$ref": "#/components/schemas/WahlParam"
          },
          {
            "name": "gebiet",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Gebiets-Id aus der Gebiete-Liste, z. B. ebene_6_id_3119"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ergebnis"
                }
              }
            }
          }
        }
      }
    },
    "/{kreis}/{termin}/{behoerde}/wahlraeume": {
      "get": {
        "summary": "Wahllokale einer Behörde",
        "parameters": [
          {
            "$ref": "#/components/schemas/KreisParam"
          },
          {
            "$ref": "#/components/schemas/TerminParam"
          },
          {
            "$ref": "#/components/schemas/BehoerdeParam"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/{kreis}/{termin}/ereignisse": {
      "get": {
        "summary": "Ticker der eingegangenen Schnellmeldungen",
        "parameters": [
          {
            "$ref": "#/components/schemas/KreisParam"
          },
          {
            "$ref": "#/components/schemas/TerminParam"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 500
            }
          },
          {
            "name": "behoerde",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/geo/{datei}": {
      "get": {
        "summary": "Geodaten als GeoJSON",
        "parameters": [
          {
            "name": "datei",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "gemeinden.geojson",
                "ortsteile.geojson",
                "wahllokale.geojson"
              ]
            }
          },
          {
            "name": "kreis",
            "in": "query",
            "description": "5- oder 8-stelliger Kreisschlüssel; ohne ihn kommt ganz Niedersachsen",
            "schema": {
              "type": "string",
              "example": "03254"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "parameters": {},
    "schemas": {
      "KreisParam": {
        "name": "kreis",
        "in": "path",
        "required": true,
        "description": "Landkreis oder kreisfreie Stadt, als Slug (hildesheim) oder 8-stelliger Schlüssel (03254000). Alle unter /kreise; Kreise mit vorhanden=false liefern keine Ergebnisse.",
        "schema": {
          "type": "string",
          "enum": [
            "braunschweig",
            "salzgitter",
            "wolfsburg",
            "gifhorn",
            "goslar",
            "helmstedt",
            "northeim",
            "peine",
            "wolfenbuettel",
            "goettingen",
            "region-hannover",
            "diepholz",
            "hameln-pyrmont",
            "hildesheim",
            "holzminden",
            "nienburg",
            "schaumburg",
            "celle",
            "cuxhaven",
            "harburg",
            "luechow-dannenberg",
            "lueneburg",
            "osterholz",
            "rotenburg",
            "heidekreis",
            "stade",
            "uelzen",
            "verden",
            "delmenhorst",
            "emden",
            "oldenburg-stadt",
            "osnabrueck-stadt",
            "wilhelmshaven",
            "ammerland",
            "aurich",
            "cloppenburg",
            "emsland",
            "friesland",
            "grafschaft-bentheim",
            "leer",
            "oldenburg-land",
            "osnabrueck-land",
            "vechta",
            "wesermarsch",
            "wittmund"
          ]
        }
      },
      "TerminParam": {
        "name": "termin",
        "in": "path",
        "required": true,
        "description": "Wahltermin. 2026: Landrats-, Kreistags-, Bürgermeister-, Rats- und Ortsratswahlen am 13. September 2026 – 2021: Kommunalwahlen am 12. September 2021 mit Stichwahlen am 26. September 2021 – amtliche Endergebnisse – 2020: Wahl des Bürgermeisters der Gemeinde Nordstemmen am 13. September 2020 mit Stichwahl am 27. September 2020. Nicht jeder Termin umfasst alle Behörden; welche Wahlen es gibt, zeigt /{kreis}/{termin}/wahlen.",
        "schema": {
          "type": "string",
          "enum": [
            "2026",
            "2021",
            "2020"
          ]
        }
      },
      "BehoerdeParam": {
        "name": "behoerde",
        "in": "path",
        "required": true,
        "description": "Wahlleitung innerhalb des Kreises, als Slug (nordstemmen) oder AGS (03254026); die Kreisbehörde heißt kreis. Welche es gibt, zeigt /{kreis}/{termin}/behoerden.",
        "schema": {
          "type": "string"
        }
      },
      "WahlParam": {
        "name": "wahl",
        "in": "path",
        "required": true,
        "description": "Wahl-Slug, z. B. kreistag, landrat, buergermeister, rat, ortsrat-roessing",
        "schema": {
          "type": "string"
        }
      },
      "Partei": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "stabiler Schlüssel, z. B. cdu"
          },
          "kurz": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "farbe": {
            "type": "string"
          },
          "stimmen": {
            "type": "integer"
          },
          "prozent": {
            "type": "number"
          },
          "listenstimmen": {
            "type": "integer",
            "description": "Stimmen für die Liste als Ganzes"
          },
          "kandidatenstimmen": {
            "type": "integer",
            "description": "Summe der Stimmen für die einzelnen Bewerber dieser Partei – die Bezugsgröße von kandidaten[].prozentInPartei"
          },
          "sitze": {
            "type": "integer"
          },
          "kandidat": {
            "type": "object",
            "description": "Personenwahl (Landrat, Bürgermeister): die antretende Person",
            "properties": {
              "name": {
                "type": "string"
              },
              "partei": {
                "type": "string"
              }
            }
          },
          "kandidaten": {
            "type": "array",
            "description": "Bewerberinnen und Bewerber dieser Partei im abgefragten Gebiet, nach Stimmen absteigend. Nur bei Verhältniswahlen (Kreistag, Rat, Ortsrat); bei Personenwahlen steht die antretende Person in kandidat.",
            "items": {
              "$ref": "#/components/schemas/Kandidat"
            }
          }
        },
        "required": [
          "key",
          "kurz",
          "name",
          "stimmen",
          "prozent"
        ]
      },
      "Kandidat": {
        "type": "object",
        "description": "Eine Bewerberin oder ein Bewerber auf der Liste einer Partei, bezogen auf das abgefragte Gebiet. Listenplätze werden je Gebiet geführt: bei der Kreistagswahl stellt jede Partei in jedem Wahlbereich eine eigene Liste auf, dieselbe Person kann also anderswo einen anderen Platz haben oder gar nicht antreten.",
        "properties": {
          "name": {
            "type": "string",
            "description": "Name in der Schreibweise der Wahlleitung"
          },
          "stimmen": {
            "type": "integer",
            "description": "Stimmen, die im abgefragten Gebiet auf diese Person entfallen sind"
          },
          "prozent": {
            "type": [
              "number",
              "null"
            ],
            "description": "Anteil an ALLEN gültigen Stimmen des Gebiets, in Prozent. Das ist der Wert, mit dem sich Bewerber verschiedener Parteien vergleichen lassen. null, solange die Zahl der gültigen Stimmen nicht vorliegt."
          },
          "prozentInPartei": {
            "type": [
              "number",
              "null"
            ],
            "description": "Anteil an den Kandidatenstimmen der EIGENEN Partei, in Prozent – der Wert, den die amtliche Wahlpräsentation ausweist. Er ist leicht misszuverstehen: Er beschreibt nur, wie sich die Stimmen innerhalb einer Liste verteilen, nicht wie stark die Person im Gebiet abgeschnitten hat. Wer auf einer kurzen Liste die meisten Stimmen holt, steht hier schnell bei 40 %, obwohl das gemessen an allen gültigen Stimmen (siehe prozent) wenige Prozent sind. Für Vergleiche zwischen Parteien ist der Wert unbrauchbar; dafür ist prozent da."
          },
          "platz": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Platz auf dem Wahlvorschlag in diesem Gebiet. null, wenn kein Wahlvorschlag vorliegt oder der Platz nicht eindeutig bestimmbar ist (zwei Bewerber derselben Liste mit exakt gleicher Stimmenzahl)."
          },
          "gewaehlt": {
            "type": "boolean",
            "description": "true, wenn die Person laut amtlicher Gewähltenliste ein Mandat erhalten hat. Steht erst fest, wenn die Sitzverteilung vorliegt."
          }
        },
        "required": [
          "name",
          "stimmen",
          "prozent",
          "prozentInPartei",
          "platz",
          "gewaehlt"
        ]
      },
      "Ergebnis": {
        "type": "object",
        "properties": {
          "termin": {
            "type": "string"
          },
          "behoerde": {
            "type": "string"
          },
          "wahl": {
            "type": "string"
          },
          "gebiet": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "ebene": {
                "type": "string"
              }
            }
          },
          "leer": {
            "type": "boolean",
            "description": "true, solange keine Zahlen vorliegen"
          },
          "stand": {
            "type": "object",
            "properties": {
              "schnellmeldungen": {
                "type": "object",
                "properties": {
                  "eingegangen": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "erwartet": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  }
                }
              },
              "vollstaendig": {
                "type": "boolean"
              },
              "status": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "z. B. Amtliches Endergebnis"
              },
              "datenstand": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "abgerufen": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "kennzahlen": {
            "type": "object",
            "properties": {
              "wahlberechtigte": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "waehler": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "wahlbeteiligung": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "ungueltig": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "gueltigeStimmzettel": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "gueltigeStimmen": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "bei Verhältniswahl bis zu drei je Stimmzettel"
              }
            }
          },
          "parteien": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Partei"
            }
          },
          "sitze": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "gesamt": {
                "type": "integer"
              },
              "verteilung": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "gewaehlte": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              }
            }
          }
        }
      },
      "Wahl": {
        "type": "object",
        "properties": {
          "termin": {
            "type": "string"
          },
          "behoerde": {
            "type": "object"
          },
          "slug": {
            "type": "string"
          },
          "typ": {
            "type": "string",
            "enum": [
              "landrat",
              "landrat-stichwahl",
              "kreistag",
              "buergermeister",
              "buergermeister-stichwahl",
              "rat",
              "ortsrat",
              "sonstige"
            ]
          },
          "typLabel": {
            "type": "string"
          },
          "titel": {
            "type": "string"
          },
          "gebiet": {
            "type": "string"
          },
          "personenwahl": {
            "type": "boolean"
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "ergebnis": {
            "$ref": "#/components/schemas/Ergebnis"
          },
          "ebenen": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "ebene": {
                  "type": "string"
                },
                "anzahl": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    }
  }
}