--- name: election-results-data description: Download official Estonian election-result XML archives and retrieve National Electoral Committee decisions. module: valimised.mjs execution: get --- # Election Results and Decisions The Riigi Valimisteenistus publishes each election on its own site (`https://.valimised.ee`) with a public JSON API behind it, and the elections before 2021 as ZIP/XML archives. No authentication. ## Retrieve **Use the module below — do not assemble the resource paths by hand.** The file name a result is keyed by differs by election type and area level, and guessing it returns a 404 that reads exactly like "this place has no result". ```js const r = await electionResult("rk2023"); show(r.totals); // { votes, mandates, parties, eligibleVoters, turnoutPct } show(r.rows.slice(0, 6)); // party, votes, sharePct, mandates, previousMandates const who = await electedMembers("rk2023"); show(who.totals); // { elected: 101, byParty: { … } } ``` `election` accepts `"rk2023"`, `"Riigikogu 2023"` or `"kohalikud valimised 2021"`. A year with no result site throws naming the ones that exist — it never falls back to the newest election of that type. `area` is a code or a name from `electionAreas(election)`. For a Riigikogu election that is one of the 12 electoral districts; for a KOV election it is a county, Tallinn/Tartu, or a municipality keyed by **its county's code and its own** (`0037-0141`, Anija vald). A bare `0141` is a 404, so pass the row's `code` or simply the name. ## What each election actually publishes Re-checked live by `elections()`, because it varies: | Site | Results | Elected members | Turnout | | --- | --- | --- | --- | | `rk2023` | yes | yes (`elected-members`) | yes | | `kov2025`, `kov2021` | yes, incl. elected councillors per municipality | inside the result | yes | | `ep2024` | **no** — the host is up and only the result files are missing | no | no | Elections before 2021 have **no JSON to read, and their sites are not gone** — corrected 2026-08-02, when this guide still called all three of them dead. `rk2019`, `ep2019` and `kov2017` all answer 200, and the first two still publish their results as HTML: `https://rk2019.valimised.ee/et/election-result/election-result.html` and `https://ep2019.valimised.ee/et/election-result/index.html`, with `https://ep2019.valimised.ee/et/elected-members/index.html` for who won. `kov2017.valimised.ee` serves only its candidate application, at `/kandidaadid`. What is missing is the JSON the 2021+ sites expose: `rk2019.valimised.ee/resources/election-result/data.json` answers HTTP 404, so `electionResult()` cannot read these elections. `rk2015.valimised.ee` presents an **expired certificate**. ```probe-limit claim `rk2019.valimised.ee/resources/election-result/data.json` answers HTTP 404 # The certificate first: a dead certificate is a transport failure, so it has no # status or body to assert alongside. GET https://rk2015.valimised.ee/ expect-error /CERT_HAS_EXPIRED/ GET https://rk2019.valimised.ee/resources/election-result/data.json expect-status 404 expect /Viga 404/ # The control is the SAME resource path on a site that does publish it. Without # it, valimised.ee 404-ing everything would confirm this claim while the two # elections we actually read had gone dark. control GET https://rk2023.valimised.ee/resources/election-result/data.json ``` For a machine-readable result use the ZIP/XML archives — `elections()` lists them off `https://www.valimised.ee/en/archive/open-data-estonian-elections`, and `fileText()` reads one. Read the HTML pages above when the archive does not answer the question. ## Committee decisions Numerical results and VVK decisions are different things and must not be mixed. For decisions use the official list at `https://www.valimised.ee/et/korraldajad/vabariigi-valimiskomisjon/otsused`, and cross-reference Riigi Teataja when legal publication matters: `https://www.riigiteataja.ee/algteksti_tulemused.html?doli=otsus&valj1=Vabariigi+Valimiskomisjon&kuvaKoik=true&sorteeri=kuupaev&kasvav=false`. ## Return - State counts from `totals`, summed in code over every party. `totals.statedVotes` is the site's own figure alongside it — when the two differ, say so rather than choosing. - A **Riigikogu district file reports votes only**: seats are allocated nationally, so `mandates` is `null` there rather than 0, and the `note` says where to get them. - Elected people are named: they hold public office and that is the result. Their birth dates are in the payload and deliberately not returned. - Cite `source` (the JSON fetched) and `page` (the election site). ## Limits - Results are published after declaration; this is not a live feed. - XML schemas and territorial units differ between election years. - Turnout for an area comes from a different file than the result, and its table lists e-votes and out-of-district votes as EXTRA rows — summing that column gave one Tallinn district 91.9% where the committee published 61.8%. The module reads the published total instead. ## Verify `metadata.json` on the same host identifies the election (`electionCode`, `electionType`, `electionYear`) — confirmed for `rk2023`, `kov2025`, `kov2021` and `ep2024`. Require a non-empty `units` array before reporting any result, and reject an HTML error page even when the requested name ends in `.zip`. ## Module API electionResult(election, {area}) -> {source, page, election, area, generated, counted, totals, rows, note} electedMembers(election, {area}) -> {source, page, election, area, totals, rows, note} electionAreas(election) -> {source, page, election, totals, rows, note}