Kohalik omavalitsus
Omavalitsuste ja ametite dokumendiregistrid
Valla, linna või ameti enda dokumendiregistri otsing: kirjad, load, lepingud ja korraldused.
Iga Eesti avalik asutus peab pidama avalikku dokumendiregistrit, kust leiab kirjad, load, lepingud ja korraldused, mille asutus on saatnud või saanud. See juhend katab kaks platvormi: Amphora, millel on 53 asutust ja peaaegu kõik omavalitsused, ning PDR, millel on 14 asutust kolmel jagatud hostil. Piiratud juurdepääsuga read on registris ainult registreerimisandmetena: pealkiri, kuupäev ja number on avalikud, sisu mitte.
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
- Kas asutus vastas kirjale ja millal?Lüganuse Kooli hoolekogu pöördus 2024. aastal Haridus- ja Teadusministeeriumi poole. Millal see pöördumine registreeriti ja millal ministeerium vastas?2026-08-03
Juhend ise
Inglise keeles, sest seda loeb mudel.
Document Registers (Amphora and PDR)
Every Estonian public body must keep a public register of the documents it sends and receives, so a citizen's letter, a raieluba, a procurement contract and a council decision are all findable in the register of the body that handled them. This guide covers the two platforms outside RIK's ADR: Amphora (53 bodies, almost all municipalities) and PDR (14 bodies on three shared hosts). For the ADR platform read ministry-document-registries; Tallinn and Tartu have guides of their own.
Do not pick the platform yourself. authority(name).register in asutused.mjs records both the URL and the platform, and docRegisterSearch() reads it from there. Passing an institution's name is the whole route:
const r = await docRegisterSearch("Haapsalu Linnavalitsus", "raieluba", { limit: 10 });
show(r);
A body whose register is on ADR, Tallinn's or Tartu's platform is refused by name, with the function that does serve it named in the error — so a wrong first guess costs one line, not a step.
Access
- Amphora: every instance is on ONE host,
https://atp.amphora.ee/<slug>, and the page calls its own JSON API. No session, no token, no POST, no__VIEWSTATE.GET /<slug>/api/item?sbrq=<term>&pageSize=4&page=1—sbrqis the register's own global search across every field.GET /<slug>/api/unit— the bodies this instance hosts, asorg_id/org_name.GET /<slug>/index.aspx?itm=<item_id>— one document's page, for a citizen to open.
- PDR:
https://dok.hm.ee,https://adr.envir.ee,https://adr.pinal.edu.ee, each shared by several bodies behind anorgId.GET /api/v1/documents.json?orgId=<n>&title=<term>&limit=20&offset=0GET /api/v1/options/name/organization.json— that host's org list, which is what to trust rather than a body's own link.GET /et/document.html?id=<guid>— the document's page.GET /api/v1/documents/<guid>.jsonis the same record as JSON.
- Slugs and orgIds are not derivable: Narva-Jõesuu is
njlv, Antsla isantslavv2017. Take them fromauthority(name).register.url, never by guessing.
Retrieve
docRegisterSearch(body, term, {limit, org}) is the whole interface. It sends only filter names verified to narrow the result, pages safely, and reports the register's own count.
termmust be at least 3 characters. Shorter is not refused by either platform — it matches nearly everything and answers HTTP 200, so a one-letter search reads as a result.sbrq=aon Rae vald returns 317,899 of its 335,473 documents.limitdefaults to 20 and is capped at 60. Amphora returns five rows per request whatever you ask for, so more rows means more round trips against a host that answers in 2–4 s. Narrow the term instead;totals.reportedalready carries the true count.orgscopes an Amphora search to one of the bodies on the instance. Take the id fromdocRegisterOrgs(body)— an inventedo2value takes over a minute to answer and comes back with zero rows.
Return
{source, page, body, platform, term, ran, totals:{shown, reported, register}, organisations, rows, note}, each row {ref, date, title, type, party, url} with an ISO date and the register's own document URL.
totals.reportedis the register's own count over the whole match. State counts from it, never by tallying rows.totals.registeris everything the instance holds, and it is there so the two can be compared — see Verify.organisationsnames the bodies sharing an Amphora instance. Haapsalu's holds 11: the linnavalitsus plus its schools, kindergartens and culture centre. A count over the instance is therefore not the municipality's own, and the module says so in its output when it happens.ran: falsemeans the request was never accepted as a search. That is a broken request, not an empty register, and must never be reported as "no such document".
When the body's own register has nothing
A body's own register can be complete, empty or thin — and the ministry that governs it registers the same correspondence. Asked what documents existed about Eesti Loodusmuuseumi direktori konkurss, a run searched the museum's 6,414-row register, found nothing about it, and drafted a teabenõue; the five documents were public in Kliimaministeerium's register the whole time.
parentRegister(name)inasutused.mjsgives that ministry whenever ITS register is searchable, with the exact call for its platform — usually not the same platform as the child's. 42 of the 156 searchable registers have one.reported: 0raises this as a printed warning by itself. A thin result does not: the module cannot tell whether five rows about an ideekonkurss answer a question about a direktorikonkurss. That judgement is yours, and it is the same rule.- Only once both registers have been searched may you say the documents are not in the public register, or draft a teabenõue for them.
Limits
pageSizeon Amphora is not a page size. It is the offset step, and the row count is fixed at five — except the value5itself, and omitting it, which return EVERY matching row. On a municipal register that is a multi-megabyte download of the whole register instead of 4 KB. The module sendspageSize=4, so consecutive pages overlap by one row and nothing is skipped, and it usespageSize=5only after a bounded request has already told it the result is small.- One instance (Viimsi) runs a rewritten portal:
/api/answers HTTP 500 and the register is served as HTML. The module falls back to it automatically and returns the same shape, minusorganisations. The trap there is that the string "Ühtegi kirjet ei leitud" appears in the hit page too, inside a JavaScript comment — the count in<span class="pager-info">is the signal, not that phrase.
claim One instance (Viimsi) runs a rewritten portal: `/api/` answers HTTP 500 and the register is served as HTML
# `/api/item` is the call the module makes on every other instance, and the one
# Viimsi answers 500 to. A removed tenant would NOT look like this — an unknown
# slug is HTTP 200 with 79 bytes of "Page Not Found" (the bullet below), so the
# 500 really is this portal rather than the slug having gone.
GET https://atp.amphora.ee/viimsi/api/item?sbrq=leping&pageSize=4&page=1
expect-status 500
expect /An error has occurred/
# The control is the HTML the module falls back to — the second half of the
# same sentence. It must answer 200, which is what tells "this instance serves
# its register as HTML" from "this instance is down", and those two get the
# citizen opposite answers.
control GET https://atp.amphora.ee/viimsi/?tab=all&q=leping&p=1
- Amphora answers a slug it does not hold with HTTP 200 and 79 bytes of
<h2>Page Not Found</h2>as text/html. JSON is the acceptance test, not the status code. - Restricted rows are published as registration data only: the title, the date and the number are public, the content is not. Private counterparties appear as initials or not at all.
partyis empty on many municipal instances — that is the register, not a parse failure. - Not covered here: WebDesktop (21 bodies) is a different contract entirely — a session cookie carried across three requests — and has its own guide,
webdesktop-document-registers.authority(name).register.guidenames the right one; send a WebDesktop register here and nothing works. A handful of registers sit behind a login page or bot detection: those rows aresearchable: falsein the institution table and the honest move is to hand the citizen the link.
Verify
A search only succeeded if ran is true. Then compare the two counts the result carries:
totals.reportedequal tototals.registermeans the platform did not recognise the filter and returned the register entire. Both platforms do this silently, HTTP 200, with a total that looks like a result —?sbrqXX=lepingon kbfi reports 265 against the correct 4, and?titleXX=eelnõuon dok.hm.ee reports 237,349 against 10,520. The module raises a printed warning when it sees this; do not report such a total as a hit count.reported: 0means the search ran and genuinely matched nothing. Say that; do not retry the same term.- Never name a document without a
urlfrom its own row.
Known-good acceptance checks, run weekly against the live registers by scripts/check-sources.mjs (issue #82).
Amphora — KBFI is deliberately a small instance, so the counter-example is not a whole-register download:
GET https://atp.amphora.ee/kbfi/api/item?sbrq=leping&pageSize=4&page=1
expect /"total":"[1-9]\d?"/
expect /"item_id":\d+/
# `total` was 4 in 2,080 bytes on 2026-08-02, against 265 documents in the
# instance. A misspelt filter name is IGNORED and the whole register comes
# back: the counter answers "total":"265" in 3,813 bytes, so it fails the
# first assertion while still returning rows. If it ever passes, a search
# that was silently dropped has become indistinguishable from a real result.
counter GET https://atp.amphora.ee/kbfi/api/item?sbrqXX=leping&pageSize=4&page=1
PDR — Keeleamet, on the shared dok.hm.ee host:
GET https://dok.hm.ee/api/v1/documents.json?orgId=148&title=leping&limit=3&offset=0
expect /"recordsFiltered":[1-9]\d{0,3},/
expect /"id":"[0-9a-f-]{36}"/
# 153 of that body's 44,420 documents on 2026-08-02. The same silent-ignore
# trap: `titleXX` returns recordsFiltered 44420 — the whole register — with
# rows attached, so only the bounded count tells the two apart.
counter GET https://dok.hm.ee/api/v1/documents.json?orgId=148&titleXX=leping&limit=3&offset=0
Module API
docRegisterSearch(body, term, {limit, org}) -> {source, page, body, platform, term, ran, totals, organisations, rows, note}