Liigu sisu juurde

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.

Milline agent selle läbi teeb

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=1sbrq is the register's own global search across every field.
    • GET /<slug>/api/unit — the bodies this instance hosts, as org_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 an orgId.
    • GET /api/v1/documents.json?orgId=<n>&title=<term>&limit=20&offset=0
    • GET /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>.json is the same record as JSON.
  • Slugs and orgIds are not derivable: Narva-Jõesuu is njlv, Antsla is antslavv2017. Take them from authority(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.

  • term must 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=a on Rae vald returns 317,899 of its 335,473 documents.
  • limit defaults 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.reported already carries the true count.
  • org scopes an Amphora search to one of the bodies on the instance. Take the id from docRegisterOrgs(body) — an invented o2 value 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.reported is the register's own count over the whole match. State counts from it, never by tallying rows.
  • totals.register is everything the instance holds, and it is there so the two can be compared — see Verify.
  • organisations names 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: false means 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) in asutused.mjs gives 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: 0 raises 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

  • pageSize on Amphora is not a page size. It is the offset step, and the row count is fixed at five — except the value 5 itself, 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 sends pageSize=4, so consecutive pages overlap by one row and nothing is skipped, and it uses pageSize=5 only 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, minus organisations. 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. party is 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.guide names 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 are searchable: false in 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.reported equal to totals.register means 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=leping on kbfi reports 265 against the correct 4, and ?titleXX=eelnõu on 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: 0 means the search ran and genuinely matched nothing. Say that; do not retry the same term.
  • Never name a document without a url from 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}