Öffentliche JSON-API für Wahlprognosen, Posterior-Draws und die Aktuelle Stimmung. Keine Authentifizierung. Nur GET.

Es gibt außerdem eine separate Polling API für Einzelumfragen unter api.zweitstimme.org (Docs: api.zweitstimme.org/docs). Diese Seite dokumentiert die Forecast API unter zweitstimme.org/api.

Doku: /docs/api/
Discovery: /api/index.json
Basis-URL: https://zweitstimme.org
CORS: Access-Control-Allow-Origin: *


Quickstart

# API discovery
curl -s https://zweitstimme.org/api/index.json | jq .

# Active state forecasts in the current window
curl -s https://zweitstimme.org/api/v2/state/index.json | jq .states

# One state forecast
curl -s https://zweitstimme.org/api/v2/state/st.json | jq '.election, .data.metadata, .data.parties'

# Raw posterior draws for the same forecast
curl -s https://zweitstimme.org/api/v2/state/st/draws.json   | jq '{generated_at, last_update: .data.last_update, n_draws: .data.n_draws, draw0: .data.draws[0]}'

# Current federal Stimmung
curl -s https://zweitstimme.org/api/v2/stimmung/federal/current.json | jq '.election, .data.parties'
const res = await fetch("https://zweitstimme.org/api/v2/state/st.json");
const { election, data } = await res.json();
console.log(election.name, data.metadata.last_poll_date, data.parties);
import json, urllib.request
with urllib.request.urlopen("https://zweitstimme.org/api/v2/state/st/draws.json") as f:
    payload = json.load(f)
print(payload["election"]["name"], payload["data"]["n_draws"])

Versioning

The current layout is transitional:

  • v1 contains the older federal forecast contract under /api/v1/federal/...
  • v2 contains the newer enveloped endpoints for state forecasts, draws, and Stimmung
  • legacy root paths like /forecast.json and /pred_probabilities.json redirect or alias into v1

So today the version numbers are partly historical and partly structural. Future cleanup may make this more uniform.

Migration Layout

A cleaner long-term layout would keep backward compatibility while making version numbers mean contract generation, not scope.

  • unversioned root paths = legacy aliases only
  • v1 = old / legacy contract
  • v2 = newer, consistent enveloped contract
  • scope belongs in the path (federal, state, stimmung), not in the version number

Backward-compatible path plan

RolePath patternNotes
Legacy aliases/forecast.json, /pred_probabilities.json, /forecast_districts.jsonKeep as redirects or content aliases for older clients
Legacy federal under versioned home/api/v1/federal/...Mirrors the older federal contract
New federal API/api/v2/federal/...Future home for a cleaner federal forecast contract
New state API/api/v2/state/...Already close to the desired structure
New state draws API/api/v2/state/{code}/draws.jsonRaw posterior simulations
New Stimmung API/api/v2/stimmung/...Already matches the desired shape

Suggested migration phases

  1. Keep current clients working

    • Leave root aliases in place
    • Leave /api/v1/federal/... untouched
    • Leave current /api/v2/state/... and /api/v2/stimmung/... untouched
  2. Add a new federal v2 surface

    • Introduce /api/v2/federal/index.json
    • Add a consistent enveloped federal forecast payload
    • Optionally add /api/v2/federal/draws.json if federal draws are to be supported the same way
  3. Mark v1 as legacy in docs

    • Keep it stable
    • Avoid adding new concepts only to v1
    • Point new integrations to v2 first
  4. Eventually simplify discovery

    • /api/index.json should describe v1 as legacy and v2 as preferred
    • The docs can then say: use v2 unless you specifically need the old federal contract

Practical end state

In that end state, consumers can infer:

  • v1 = older compatibility layer
  • v2 = modern API contract
  • federal / state / stimmung = resource namespace

That makes the API easier to explain, easier to document, and easier to extend without teaching users that version numbers also encode product scope.



Which Endpoint Do I Need?

Use caseStart here
Discover what exists/api/index.json
Legacy / older federal forecast contract/api/v1/federal/index.json
Active state forecasts/api/v2/state/index.json
Raw state posterior draws/api/v2/state/{code}/draws.json
Current federal Stimmung/api/v2/stimmung/federal/current.json
Full federal Stimmung time series/api/v2/stimmung/federal.json
State Stimmung catalog/api/v2/stimmung/state/index.json

Forecast vs. Stimmung

  • Forecast = model output for election day: point estimates, uncertainty intervals, scenario probabilities, and optionally posterior draws.
  • Stimmung = smoothed latent support by calendar day. No seat scenarios.
  • election always names the next relevant election for that scope; it is not itself the forecast timestamp.

Common Response Envelope

Versioned endpoints use a common top-level structure:

{
  "api_version": "v2",
  "generated_at": "2026-08-18T08:06:53Z",
  "election": {
    "id": "st_2026-09-06",
    "name": "Landtagswahl Sachsen-Anhalt",
    "date": "2026-09-06",
    "scope": "state",
    "state_code": "ST",
    "date_is_estimated": false
  },
  "data": {}
}
FieldTypeMeaning
api_versionstringContract generation, currently "v1" or "v2"
generated_atstringAPI build time in ISO-8601 UTC
electionobjectWhich election this payload refers to
dataobject or arrayEndpoint-specific payload
as_ofstring, optionalUsed by Stimmung endpoints
archivedboolean, optionalPresent on archive responses

Always inspect election. The same path can refer to a different election in a later cycle.


Try-It Checks

Use these to sanity-check clients quickly.

# Does the endpoint exist right now?
curl -i https://zweitstimme.org/api/v2/state/st.json

# Show the active state codes currently in the forecast window
curl -s https://zweitstimme.org/api/v2/state/index.json | jq -r '.states[].state_code'

# Check whether draws are available for each active state
curl -s https://zweitstimme.org/api/v2/state/index.json   | jq -r '.states[] | [.state_code, .path, (.draws // "-")] | @tsv'

# Extract one day's Stimmung from the full series
curl -s https://zweitstimme.org/api/v2/stimmung/federal.json   | jq '.data.by_date["2026-08-01"]'

v1 Federal Forecast API

v1 is the older federal forecast surface. Canonical entry point: /api/v1/federal/index.json.

Endpoints

MethodPathdata payload
GET/api/v1/federal/index.jsonEndpoint catalog and legacy redirects
GET/api/v1/federal/forecast.jsonArray of party forecast rows
GET/api/v1/federal/pred_probabilities.jsonHurdles, coalitions, largest party probabilities
GET/api/v1/federal/forecast_districts.jsonDistrict-level first/second-vote forecast
GET/api/v1/federal/archive/index.jsonArchived run catalog
GET/api/v1/federal/archive/{YYYY-MM-DD}.jsonOne archived federal forecast

Party rows in forecast.json

FieldUnitMeaning
name, name_engParty label
_rowParty code such as cdu, afd
value, ypercentage pointsPoint estimate
low, highpercentage pointsApprox. 83% interval
low95, high95percentage pointsApprox. 95% interval
colorhexUI color

pred_probabilities.json

Probabilities are stored as shares from 0 to 1, not percentages.

PrefixExampleMeaning
hurdle_*hurdle_fdpParty clears 5% threshold
maj_*maj_cdu_csu_spdCoalition wins seat majority
prob_*_largestprob_cdu_largestParty is largest

Legacy root aliases

Legacy pathCanonical v1 path
/forecast.json/api/v1/federal/forecast.json
/pred_probabilities.json/api/v1/federal/pred_probabilities.json
/forecast_districts.json/api/v1/federal/forecast_districts.json

For older clients, note that the root paths now resolve to an enveloped payload, not a bare array.


v2 State Forecast API

Active only within roughly the 90-day forecast window before election day. Outside the window, state forecast endpoints return 404. After election day, the last forecast is moved to the archive.

Endpoints

MethodPathMeaning
GET/api/v2/state/index.jsonActive states and archive link
GET/api/v2/state/{code}.jsonOne active state forecast
GET/api/v2/state/{code}/draws.jsonRaw posterior draws for that forecast
GET/api/v2/state/archive/index.jsonArchived state forecast catalog
GET/api/v2/state/archive/{code}_{YYYY-MM-DD}.jsonOne archived state forecast
GET/api/v2/state/archive/{code}_{YYYY-MM-DD}/draws.jsonDraws for one archived state forecast

{code} is lowercase state shorthand such as st, be, mv.

District, candidate, and preview-only Wahlabend assets are not part of this public API.

Active state catalog

Example fields from /api/v2/state/index.json:

{
  "api_version": "v2",
  "generated_at": "2026-08-18T08:06:53Z",
  "forecast_window_days": 90,
  "states": [
    {
      "state_code": "ST",
      "path": "/api/v2/state/st.json",
      "draws": "/api/v2/state/st/draws.json",
      "active": true,
      "election": {
        "id": "st_2026-09-06",
        "name": "Landtagswahl Sachsen-Anhalt",
        "date": "2026-09-06"
      }
    }
  ]
}

Prefer the index over hardcoding active state codes.

Forecast payload: /api/v2/state/{code}.json

{
  "metadata": {
    "state_code": "ST",
    "election_id": "st_2026-09-06",
    "election_name": "Landtagswahl Sachsen-Anhalt",
    "election_date": "2026-09-06",
    "last_poll_date": "2026-08-16",
    "asof_date": "2026-08-16",
    "last_update": "2026-08-18T08:06:12Z",
    "lead_horizon_days": 19,
    "model": "state-models lr 19_polls ...",
    "n_draws": 4000,
    "draws_path": "/api/v2/state/st/draws.json"
  },
  "parties": [
    { "party": "CDU", "party_code": "cdu", "fit": 23, "low": 16, "high": 29 }
  ],
  "scenarios": {
    "items": [
      { "id": "largest_party_afd", "category": "largest_party", "probability": 97 }
    ]
  }
}
FieldUnitMeaning
metadata.last_poll_datedateNewest poll included in the forecast
metadata.asof_datedateForecast input date anchor
metadata.last_updatetimestampWhen this forecast was computed / exported
metadata.lead_horizon_daysintegerDays until election used in the model
metadata.n_drawsintegerNumber of posterior draws behind summary and scenarios
metadata.draws_pathpathLink to draws endpoint
parties[].fitpercentage pointsPoint estimate
parties[].low, parties[].highpercentage pointsApprox. 83% interval
scenarios.items[].probabilitypercent 0-100Scenario probability

Draws payload: /api/v2/state/{code}/draws.json

These are the same simulations that produce the summary forecast and scenario probabilities. They are regenerated whenever the state forecast is rerun.

{
  "api_version": "v2",
  "generated_at": "2026-08-18T08:06:53Z",
  "election": {
    "id": "st_2026-09-06",
    "scope": "state",
    "state_code": "ST"
  },
  "data": {
    "n_draws": 4000,
    "unit": "share",
    "last_update": "2026-08-18T08:06:12Z",
    "asof_date": "2026-08-16",
    "last_poll_date": "2026-08-16",
    "parties": ["cdu", "spd", "gru", "fdp", "lin", "afd", "bsw", "oth"],
    "draws": [
      {
        "cdu": 0.23,
        "spd": 0.07,
        "gru": 0.05,
        "fdp": 0.03,
        "lin": 0.13,
        "afd": 0.41,
        "bsw": 0.04,
        "oth": 0.04
      }
    ]
  }
}
FieldMeaning
unit"share" means 0-1 vote shares, not percentages
n_drawsNumber of simulations
last_updateForecast-run timestamp in UTC
asof_date, last_poll_dateTiming metadata repeated for convenient downstream use
partiesDraw column order / available party codes
draws[]One normalized simulation per row
envelope generated_atAPI publish time, distinct from model run time

Useful test calls

# One-line metadata check
curl -s https://zweitstimme.org/api/v2/state/st.json   | jq '.data.metadata | {state_code, last_poll_date, last_update, n_draws, draws_path}'

# First draw and timestamps
curl -s https://zweitstimme.org/api/v2/state/st/draws.json   | jq '{generated_at, last_update: .data.last_update, draw0: .data.draws[0]}'

# Compare summary estimate with draw availability across active states
curl -s https://zweitstimme.org/api/v2/state/index.json   | jq -r '.states[] | [.state_code, .path, (.draws // "-")] | @tsv'

Archive policy

  • After election day, active files move to /api/v2/state/archive/...
  • Archives begin when this API began publishing them; there is no historical backfill for older cycles
  • Archive responses may include "archived": true
  • Draws move with the forecast: /api/v2/state/archive/{code}_{YYYY-MM-DD}/draws.json

v2 Stimmung API

Stimmung is a Kalman-smoothed daily latent support series. Days without a new poll are still present.

Endpoints

MethodPathMeaning
GET/api/v2/stimmung/federal/current.jsonFederal current day
GET/api/v2/stimmung/federal.jsonFull federal history
GET/api/v2/stimmung/state/index.jsonState catalog
GET/api/v2/stimmung/state/{code}/current.jsonOne state, current day
GET/api/v2/stimmung/state/{code}.jsonOne state, full history

Current payload example

{
  "as_of": "2026-08-11",
  "parties": {
    "CDU/CSU": 21.0,
    "AfD": 27.9,
    "SPD": 12.1,
    "GRÜNE": 14.2,
    "LINKE": 11.5,
    "BSW": 2.9,
    "FDP": 4.6,
    "Sonstige": 5.8
  },
  "uncertainty_low": { "CDU/CSU": 20.2, "AfD": 27.1 },
  "uncertainty_high": { "CDU/CSU": 21.8, "AfD": 28.7 },
  "trends": { "CDU/CSU": -0.2, "AfD": 0.3 },
  "active_parties": ["CDU/CSU", "AfD", "SPD", "GRÜNE", "LINKE", "BSW", "FDP"]
}
FieldMeaning
as_ofCalendar date represented by this payload
partiesSupport in percentage points
uncertainty_*Uncertainty band around the latent estimate
trendsShort-run movement in percentage points
active_partiesParties currently treated as active in the model

Read a historical day

There is no ?date= query parameter because the API is statically hosted. Load the series and index locally:

const payload = await fetch(
  "https://zweitstimme.org/api/v2/stimmung/federal.json"
).then((r) => r.json());

const day = payload.data.by_date["2026-08-01"];
console.log(day.parties);

Errors, caching, and availability

SituationBehavior
Unknown path404
No active state forecast404
State outside forecast windowstate forecast missing, Stimmung still available
Deploy or maintenancefiles may briefly lag or disappear

There is no auth layer and no custom application rate limiting beyond normal CDN / hosting behavior. Cache responsibly using ETag, Last-Modified, or your own TTL.


Internal /data/ paths

The website UI also reads /data/forecast_state_*.json, /data/stimmung_*.json, and related assets. Those are internal implementation paths, not a stable public contract. Integrations should use /api/....


Usage terms

  • Non-commercial use
  • Source attribution: zweitstimme.org
  • No guarantee of availability, completeness, or correctness
  • Forecasts and Stimmung are model outputs, not official results