Seadused ja valitsemine
Riigikogu avaandmed
Eelnõu tee algusest lõpuni, hääletused, saadikud, komisjonid, päevakorrad ja stenogrammid.
Riigikogu avaandmete liides (api.riigikogu.ee) katab parlamendi töö tervikuna: ühe eelnõu tee algatajast jõustumiseni, hääletused koos iga saadiku häälega, saadikud, komisjonid, päevakorrad ja stenogrammid. Eelnõu kirjes on ka arvamused, mille huvirühmad, ministeeriumid ja advokaadibürood esitasid, muudatusettepanekud ja komisjonide protokollid. Komisjoni istungi sõnasõnalist teksti ei ole olemas: komisjonis öeldu kohta on ainult protokoll, kus on nimed, ettepanekud ja hääletused.
Mida su agent peab oskama
GETPiisab lehe tõmbamisest. Kõik vajalikud päringud on tavalised HTTPS-päringud. Iga agent, kes oskab veebilehe alla laadida, saab selle juhendi läbi teha.
Vastused, mis on siit tulnud
- Mille üle Riigikogu tegelikult hääletas?Millised hääletused toimusid Riigikogu täiskogus 2026. aasta juunis?2026-08-04
Juhend ise
Inglise keeles, sest seda loeb mudel.
Riigikogu Open Data API
Vetted module (use this first)
riigikogu.mjs ships in https://kodanikukratt.ee/kratt-agent.tar.gz (plain ESM, Node 22+) and is re-verified against the live source. Call it instead of hand-writing these requests. Everything it does is also written out below as ordinary HTTPS requests, so this recipe stands alone without it.
// The whole life of one bill: initiators, opinions, committee decisions, readings, votes, texts.
const t = await draftTrace("418 SE");
show({ title: t.title, stage: t.stage, initiators: t.initiators, counts: t.counts, page: t.page });
show(t.opinions.map((o) => ({ date: o.date, title: o.title })));
// One committee's sittings, agenda items and invited officials.
const c = await committeeSittings("julgeolekuasutuste järelevalve erikomisjon", { from: "2026-01-01", to: "2026-06-30" });
show({ committee: c.committee, totals: c.totals, source: c.source }); // counts from `totals`, not from printed rows
// How one MP voted, and every plenary vote in a window with tallies.
const r = await memberVotings("Lauri Hussar", { from: "2026-05-01", to: "2026-07-24" });
show({ member: r.member, votes: r.votings.length, truncated: r.truncated, page: r.page });
show(await sittingVotings({ from: "2026-06-01", to: "2026-06-30" }).then((v) => [v.totals, v.sessionNote, v.source]));
draftTrace(markOrUuid, {membership, draftTypeCode}):"418 SE"→ uuid, then the 46 KB draft detail flattened to ~14 KB.introductionis the seletuskiri's own purpose statement;readings[].events[]carries committee decisions, plenary statuses, vote links and protocol PDFs.memberVotings: pages that member's own votes, presence checks (KOHALOLEKU_KONTROLL) dropped, newest first.truncated: truemeans older votings were omitted — say so.sittingVotings: one entry per sitting withinFavor/against/neutral/abstainedper voting.totals/countsare computed over the whole result set, so they stay right when you print only a slice (observed failure: answering "13 sittings, 81 votings" against data holding 15 and 106). State every number from them, never from the rows you printed.- Also exported:
findMember(name),plenaryMembers(),findUsergroup(name),draftCount(filters)(see "Bill statistics"). - All retry HTTP 429 from one shared budget, so a rate-limited run degrades to a partial answer rather than burning the step cap.
The endpoints below are the fallback for what the module does not cover, or if a call throws.
Avoid when
- You need election outcomes (use election archive skill).
- You need what was said in a committee — committee verbatim does not exist. The protocol PDF is the record; see "Committees" below.
- You need the ministry drafting stage or the government's own decision — those are outside this API (ministry document registers, Riigikantselei).
Primary endpoints
- OpenAPI contract: https://api.riigikogu.ee/v3/api-docs (72 paths, 181 KB — too large to read whole)
- Draft search and draft detail:
…/api/volumes/drafts?…and…/api/volumes/drafts/{uuid}?lang=ET&querySteno=false— see the two sections on bills below - Votes in a window, tallies inline:
https://api.riigikogu.ee/api/votings?startDate=2025-01-01&endDate=2025-01-31&lang=ET - One voting, with per-member votes:
https://api.riigikogu.ee/api/votings/{uuid}?lang=ET - One member's votes (with each decision inline):
https://api.riigikogu.ee/api/votings/plenary-member/{memberUuid}?startDate=2026-05-01&endDate=2026-06-30&lang=ET&size=100&page=0 - One member's committee votes:
https://api.riigikogu.ee/api/votings/committees?userUuid={memberUuid}&startDate=2026-01-01&endDate=2026-06-30&lang=ET - Plenary agendas:
https://api.riigikogu.ee/api/agenda/plenary?startDate=2025-01-01&endDate=2025-01-31&lang=ET - Documents, stenograms and 1000-signature petitions: see their own sections below
- Proceeding-stage codes:
https://api.riigikogu.ee/api/classifiers/menetlusetapid - Public fallbacks: https://www.riigikogu.ee/tegevus/kalender/ and https://www.riigikogu.ee/tegevus/stenogrammid/
Members: resolve the UUID from the name first
Every per-member endpoint is keyed by UUID, and a member UUID is NEVER guessable — a fabricated one returns an empty page that looks like "this MP has no votes". Ask /api/plenary-members for the name first and read the uuid out of the response:
https://api.riigikogu.ee/api/plenary-members?name=Kaljulaid&lang=ET→ one record, ~3 KB, withuuid,factions,committees, electoral district.namematches a case-insensitive substring of the first OR last name, so a surname alone is enough; a full name (name=Raimond Kaljulaid) also works. Letters, spaces, hyphens and apostrophes only — other characters return HTTP 400.- Default is sitting members only. For someone who has left, add
status=ALL(orstatus=INACTIVE); values are uppercase. - Never request
/api/plenary-memberswithoutname=: the full list of 101 members is ~440 KB and gets cut at the 20 000-character response limit, i.e. about four members, alphabetically from "Aab". A singlename=query is also the cheaper request.
Then feed that uuid into /api/votings/plenary-member/{uuid}, /api/votings/committees?userUuid={uuid}, /api/statistics/votings/member/{uuid}, or /api/plenary-members/{uuid}.
Committees: same rule, plus hideInactive=true
Committees, erikomisjonid, factions and delegations are all "usergroups", keyed by UUID and equally unguessable:
https://api.riigikogu.ee/api/usergroups?name=julgeolek&hideInactive=true&lang=ET→ the one active match, ~0.4 KB.nameis a case-insensitive substring and accepts spaces and Estonian letters.- Always pass
hideInactive=true. The register keeps every unit since 1992:?typeCode=ERIKOMISJONalone returns 40 rows, 36 of them dissolved, several near-identical in name. An agenda query against a dissolved committee returns an empty list that reads as "it never met". ?typeCode=ALALINE_KOMISJON&hideInactive=true→ the 11 standing committees;?typeCode=ERIKOMISJON&hideInactive=true→ the 4 special/inquiry committees. Other codes:FRAKTSIOON,DELEGATSIOON,PARLAMENDIRYHM,UURIMISKOMISJON.- Never request
/api/usergroupsbare: 347 rows, 98 KB, over the response limit.
Then /api/agenda/usergroup/{uuid}?startDate=…&endDate=… → {weekStartDate, weekEndDate, sittings[]} (the week* names are wrong: the window is whatever you asked for). Each sitting has sittingDateTime, sittingTypeCode (KORRALINE/ERAKORRALINE/UHISISTUNG), agendaItems[] (title, and invitees as free text where officials were summoned) and protocolDocuments[] with the files.
For "which committees met at all" across a date range rather than one named committee, /api/events?startDate=…&endDate=…&type=COMMITTEE&lang=ET lists every committee's sittings per day — but 5 days is 144 KB, so prefer the single-committee route whenever the question names one.
Committee verbatim does not exist: ?type=IK on /api/steno/verbatims is silently ignored and returns the plenary transcript for those dates instead. What was argued in a committee is only in the protocol PDF — not word-for-word, but with named speakers, each amendment and whether it was voted. Read it with pdfText(download) from pdf.mjs.
Attendance opens every protocol (format stable 2024–2026 across committees): Juhataja:, Protokollija:, then Võtsid osa: with Komisjoni liikmed: and Komisjoni ametnikud:, then Puudusid: and Kutsutud: (guests, tagged per agenda item). Read whole labelled blocks — names wrap across lines, the last is joined with "ja", and the chair is NOT repeated under Komisjoni liikmed:. A missing Puudusid: line means nobody was recorded absent. One protocol is one sitting: counting attendance over a period means reading every protocol in it — enumerate via committeeSittings() → protocols[].download, not document title search (registered titles are inconsistent: "PÕSK protokoll nr 96"), and report a sitting with no protocol as a gap, not an absence.
Trace one bill: what lives where
GET /api/volumes/drafts/{uuid} is the whole story in one 30–50 KB response, and it is the same 28 top-level keys on every bill — do not go hunting. The table maps the ones that answer questions; the rest are copyOrOriginal, membership, titleWithMarkAndTypeCode, initialTitle, activeDraftStatus, initiated, joined, accepted, amendmentsDeadline, responsibleOfficer, descriptors[], relatedVolumes[], preProcedureDocuments[], preProcedureVolumes[], _links.
| Question | Where |
|---|---|
| Who initiated it, and why | initiators[].name (an MP, a faction, a committee, or the classifier Vabariigi Valitsus) + introduction |
| Which committee leads it | leadingCommittee.name, responsibleMember.name |
| Who wrote in (associations, ministries, law firms, WHO…) | opinions[] — {date, title, files[]}, one per submission; the sender is in the title |
| Proposed changes to the text | amendments[] — {title, files[]} (documentType: "muudatusettepanek") |
| What happened when, and what each committee decided | readings[] — readingCode is INITIATION, FIRST_READING, SECOND_READING, THIRD_READING, EFFECTUATION → proceedingEvents[] with date, sittingTitle, status/decisionCode, committeeDecisions[], votings[], protocolFiles[] (the committee protocol PDF, already here — no document search needed) |
| How the plenary voted | readings[].proceedingEvents[].votings[] gives the voting uuid only — follow /api/votings/{uuid} for inFavor/against/abstained/present |
| Signature and publication | the EFFECTUATION reading: statuses SAADETUD_VABARIIGI_PRESIDENDILE, VALJAKUULUTATUD, AVALDATUD_RIIGITEATAJAS |
| The bill text, seletuskiri, impact analyses at each stage | texts[] — {readingCode, document:{documentType}, file:{fileName, _links.download.href}}. documentType is algtekst, lugemiseTekst, lopptekst, lisadokumendid or vabariigiPresidendiOtsus; the seletuskiri is a file inside those, named like Seletuskiri_.pdf, not a documentType of its own |
| Whether it became law | activeDraftStage: VASTU_VOETUD passed, TAGASI_LYKATUD rejected, TAGASI_VOETUD withdrawn, VALJA_LANGENUD_KOOSEISU_LOPPEMISEGA lapsed with the composition |
Mark → uuid: ?mark=418&draftTypeCode=SE returns 8 bills — a mark is reused every Riigikogu composition. Add membership=15 for the current one (/api/memberships/current gives the number), or pick the record with the highest membership. draftTrace() does this for you.
Bill statistics: cohorts, proposers, and what "passed" means
draftStats({from, to, byProposer}) is the entry point for any "how many bills / what share passed / by whom / by month" question. Two requests for the government split; ~28 with byProposer: true. Read the note and proposerNote it returns, which restate the two rules below against the cohort you actually asked for.
const s = await draftStats({ from: "2025-01-01", to: "2025-12-31", byProposer: true });
show({ asOf: s.asOf, cohortTotal: s.cohortTotal, totals: s.totals, proposers: s.proposers, page: s.page });
show([s.note, s.proposerNote, s.stages]);
show(s.months.map((m) => [m.month, m.initiated, m.passed, m.pending, m.passedOfInitiated, m.passedOfDecided]));
- Two rates, and neither one is "eventually passed". The register knows whether a bill HAS passed, never whether a pending one will.
passedOfInitiatedkeeps bills still in proceedings in the denominator;passedOfDecidedcounts only terminal outcomes (pendingisproceedingStatus: "IN_PROCESS", already in the search response). 2025: 106 of 175 = 60.6% of initiated, 65.4% of the 162 decided, 13 still at second reading (stages). Say which denominator you used. - A cohort is by INITIATION month, outcome as of
asOf. A bill initiated 2025-12-18 and passed 2026-05-20 counts in December — 44 of the 106 bills initiated in 2025 reached their current stage in a later year, and every December one did. "Share of December's bills that passed" and "bills passed in December" are different questions; name which you answered. A month with no bills initiated has anullrate, not 0%. - Proposer categories are mutually exclusive, by precedence: government > faction > committee > members. Factions and committees are BOTH
type: "usergroup", and 12 of the 2025 bills name a faction together with individual MPs — counted once, under the faction, or the categories stop summing to the cohort. 2025: 103 government (96 passed), 55 faction (2), 4 committee (4), 13 MPs (4). - The draft SEARCH response carries no
initiators[]— only/api/volumes/drafts/{uuid}does. Do not fetch one detail record per bill: subtractinitiatorUuidsets first, then ask only about what is left (28 requests for 2025 against 176, identical bill for bill, verified 2026-08-02). Pace it — see "Request contract". - One number on its own:
draftCount({from, to, initiatorUuid, stage})→page.totalElementsin one request. Vabariigi Valitsus isinitiatorUuid=65e1db80-49f1-4c5b-b1b6-539b7efd199f; other initiator UUIDs fromfindUsergroup()/findMember(), or/api/lists/draft-initiators(744 entries: 626 MPs, 113 usergroups, 5 classifiers — 73 KB, filter it down).
Documents: protocols, opinions, amendments
/api/documents searches every registered document; /api/documents/{uuid} adds files[] with _links.download.href and volume (the sitting the document belongs to).
- Only the 20 types in
/api/lists/document-typesare actually searchable. The query accepts 42, butseletuskiri,muudatusettepanek,opinionDocument,algtekstandlopptekstreturntotalElements: 0with any date window — those are bill attachments and are reachable ONLY through the draft-detail response above. Types that do work:protokoll(2 318 since 2024),letterDocument,decisionDocument,commissionOpinionDocument,aruanne,collectiveAddressDocument,interpellationsDocument,writtenQuestionDocument,lisadokumendid,yldine. titleis a case-insensitive substring:?title=põhiseaduskomisjoni istungi protokoll&documentType=protokollplus a date window → 7 hits, 2.8 KB.createdStart/createdEndfilter the registration date, not the sitting date — a protocol of the 4 June sitting was registered on 10 June. Widen the window by a week and read the real date fromvolume.title.- 1000-signature petitions:
/api/documents/collective-addressesreturns all 219 with their full status trail (statuses[]— registered → taken into proceedings → committee decision → closed) — but it ignoressizeand always sends 1.4 MB, so filter it down rather than reading it whole; a single petition via/api/documents/collective-addresses/{uuid}is 7 KB.
Stenograms (chamber debate)
/api/steno/verbatims?startDate=…&endDate=… returns [{date, title, plenarySession, agendaItems[]}], and each agenda item has events[] of {type: "SPEECH", date, speaker, text} — speech-level, with the speaker as a plain string. One sitting day is ~185 KB, so filter it and filter by speaker or by agenda-item title. type (IS/IT/IK) is accepted but has no effect.
Request contract
- No authentication. Verified 2026-08-02 against OpenAPI version
2.21.8. startDate/endDate/createdStart/createdEnduseyyyy-MM-dd. Both dates are required for/api/votingsand/api/steno/verbatims, optional for/api/agenda/plenary.- Draft search accepts
title,reference,mark,membership,draftTypeCode,proceedingStatus,activeDraftStage,leadingCommitteeUuid,responsibleMemberUuid,initiatorUuid, initiation/amendment date bounds,page,size,sort. draftTypeCodeacceptsUA,DE,PE,AE,TK,SE, orOE(SE= seaduseelnõu). Use the classifier codes; do not translate them.langacceptsET,RU, orENand defaults toETwhere supported.- Paged endpoints (
/api/volumes/drafts,/api/documents) answer{_embedded:{content:[…]}, page:{size, totalElements, totalPages, number}}./api/usergroups,/api/votings,/api/steno/verbatimsand/api/lists/*answer a bare array and do not page. - HTTP 429 is rate limiting, not an unavailable endpoint. Pace any loop at about one request per second — measured 2026-08-02: back to back, 1 request in 25 got through and 24 were 429; at 500 ms, 13 of 25; at 800 ms, 40 of 40 clean.
Downloading files
_links.download.hrefon any file →https://api.riigikogu.ee/api/files/{uuid}/download. CheckaccessRestrictionType: anything other thanPUBLICwill not open, and that is a fact to report, not a failed retrieval.- Prefer the
.pdfentry. The.asicebeside it is a ZIP wrapping the very same PDF plus signatures — unzipping it gains nothing. Read PDFs withpdfText(url)frompdf.mjs. - Some
.htmlfiles (e-mail exports, e.g. committee correspondence) come back asContent-Type: text/htmlwith no charset while the bytes are windows-1257.res.text()turns every õ/ä/ö/ü into�— 79 of them in one 6 KB letter. Decode explicitly:new TextDecoder("windows-1257").decode(Buffer.from(await res.arrayBuffer())).
The sitting calendar bounds every date range
RKKTS § 45 lg 1: korralised istungjärgud run from the second Monday of January to the third Thursday of June, and from the second Monday of September to the third Thursday of December. Outside those windows the chamber does not sit, so an empty result is correct rather than a failed retrieval — which is why a query for "June 2026" legitimately stops at 18 June, and why a year has no bills initiated in July or August. When a range you were asked about runs past the end of a session, say so and name the reason, or the citizen reads the last date in the data as the last thing that happened. Check the boundary against the year in question rather than assuming a date.
Reporting
- Preserve UUIDs, draft marks, stages, classifier codes, timestamps, sitting metadata, speaker names and vote values as returned, and cite the exact URL you fetched.
- Give the citizen a page they can open.
api.riigikogu.eeis a machine endpoint; a bare API URL is not a usable citation for a person. Every module result carriespagefor this; the human pages take the SAME uuid the API returns — billhttps://www.riigikogu.ee/tegevus/eelnoud/eelnou/{draftUuid}/, memberhttps://www.riigikogu.ee/riigikogu/koosseis/riigikogu-liikmed/saadik/{memberUuid}/. Cite the human page for the reader, keep the API URL as evidence, and never offerhttps://api.riigikogu.ee/on its own. - Do not infer that a scheduled agenda item was completed; join to votes, stenograms or documents when outcomes matter.
- An
opinions[]entry proves someone filed a document, not that they were heard or that anything changed — say which it is.
Module API
memberVotings(name, {from, to}) -> {source, page, member, truncated, presenceChecks, votings, rows, totals} sittingVotings({from, to}) -> {source, totals, sessionNote, sittings, rows} draftTrace("418 SE") -> {source, mark, title, stage, initiated, introduction, initiators, leadingCommittee, counts, opinions, amendments, readings, texts} committeeSittings("põhiseaduskomisjon", {from, to}) -> {source, committee, totals, sittings} draftStats({from, to, byProposer}) -> {source, page, asOf, note, totals, proposers, months, stages, proposerNote, truncated, cohortTotal}