Kohalik omavalitsus
Tallinna dokumendiregister
Tallinna linnaosade, ametite ja koolide kirjavahetus, käskkirjad ja lepingud.
Tallinna ametiasutuste dokumendiregister (dhs.tallinn.ee) on see, mida tallinlane dokumendiregistri all mõtleb: kirjad sisse ja välja, teabenõuded, käskkirjad, lepingud ja avaldused. Registris on 273 registripidajat: kaheksa linnaosa valitsust, linnakantselei ning linna ametid koos oma koolide, lasteaedade ja kultuurikeskustega. Volikogu vastuvõetud otsuseid ja määrusi siin ei ole, need on eraldi registris TEELE.
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
- Kes sai loa puid maha võtta?Mis dokumente registreeris Põhja-Tallinna Valitsus 2024. aastal raielubade kohta? Anna dokumendi kuupäev ja viit.2026-08-04
Juhend ise
Inglise keeles, sest seda loeb mudel.
Tallinn Document Register (dhs.tallinn.ee/atp)
Tallinn runs two public registers and they hold different things.
- This one —
dhs.tallinn.ee/atp, "Tallinna ametiasutuste dokumendiregister" — is the correspondence register: letters in and out, teabenõuded, käskkirjad, contracts, applications. It is what a citizen means by dokumendiregister, and it is the register the eight district governments and Tallinna Linnavalitsus keep. - TEELE —
teele.tallinn.ee, guidetallinn-council-documents— is the city COUNCIL's adopted legal acts: otsused, määrused, eelnõud. A raieluba, a reply to a resident's letter or a contract is not in it.
Asking one for the other's records returns a clean, empty-looking answer. Read the question first: an act the council passed is TEELE, everything a body sent or received is here.
Access
Everything is a plain GET over HTTPS. No session, no cookie, no token, no JavaScript.
Fetch this register directly; do not route it through tallinn.mjs.
dhs.tallinn.ee blocks by network address rather than by request shape: measured
2026-08-02, the identical URL answered HTTP 403 Forbidden ... You don't have permission to access /atp/ from a datacentre address and 200 from an ordinary one. Whether it
answers you therefore depends on where your requests leave from, not on how you make
them, and a 403 here is this block rather than a bad URL. Build the URL below and fetch
it with whatever ordinarily fetches a page for you.
That costs you the body list, which is the one thing you cannot guess. The asutus id
exists only in the register's own dropdown — Kesklinn is 5, Haabersti 12, Põhja-Tallinn 21 —
and a wrong id answers HTTP 200 with somebody else's documents, or with zero rows, which
reads as an empty register. So fetch https://dhs.tallinn.ee/atp/ first and
read the <select name="asutus"> options out of it. Note the register calls Tallinna
Linnavalitsus Tallinna Linnakantselei. Never guess an id, and never report a count without
checking that the rows' own Asutus: line names the body you asked for — that echo is the only
proof the filter applied.
- Register:
https://dhs.tallinn.ee/atp/ - Search:
https://dhs.tallinn.ee/atp/?c_tpl=1092&command=search&asutus=<id>&pealkiri=<term>&pealkiri_meetod=1&page=1 - One document:
https://dhs.tallinn.ee/atp/?c_tpl=1092&command=details&dok_id=<id> - Results are 15 rows a page, newest first, and page past the last one to get zero rows with the count unchanged.
c_tpl=1092 is not optional. Without it the same query answers HTTP 200 with the register's front page — 35,847 bytes, no error, no count line and no rows — which is a request that never searched dressed as a healthy page.
claim Without it the same query answers HTTP 200 with the register's front page
GET https://dhs.tallinn.ee/atp/?command=search&asutus=12&pealkiri=leping
expect-status 200
# The front page is the one page carrying `limitform`, the institution
# selector. A results page has `documents_container` and a `Kokku leiti` line
# and no limitform at all, so this pattern is exactly "you got the front page".
expect /name="limitform"/
# The control is the SAME query with c_tpl back. It must fail the pattern
# above — that is what separates "c_tpl is required" from "this register now
# serves its front page to everything", which would confirm the claim while
# meaning the opposite.
control GET https://dhs.tallinn.ee/atp/?c_tpl=1092&command=search&asutus=12&pealkiri=leping
Retrieve
tallinnDocuments(body, term, {limit, match, from, to, direction}) in tallinn.mjs is the whole interface. It sends only names verified to narrow the result, resolves the body against the register's own list, pages safely and reports the register's own count.
const r = await tallinnDocuments("Põhja-Tallinna Valitsus", "raieluba", { from: "2025-01-01", to: "2025-12-31" });
show(r);
bodyis an institution name; aliases work ("Kesklinn"). Passnullto search every body at once — right for "which Tallinn body handled this", wrong for "what does Haabersti have".termmatches the title as a substring, minimum 3 characters.matchis"all"(default),"phrase"or"any".from/totakeyyyy-mm-ddand are sent in the register's own dd.mm.yyyy.directionis one ofsisemine,sisse,sissetulev,välja,väljaminev.limitdefaults to 20, caps at 60.
tallinnBodies("<fragment>") lists the registrars. The register holds 273 of them and the institution table holds nine: the eight district governments and Tallinna Linnavalitsus (which the register labels Tallinna Linnakantselei). The rest are the city offices — Haridusamet, Linnaplaneerimise Amet, Linnavaraamet, Sotsiaal- ja Tervishoiuamet — and the schools, kindergartens and culture centres under them, which is the only route this repo has into a Tallinn school's register.
The asutus ids come from that list and nowhere else. They are not registry codes and not derivable: Kesklinn is 5, Haabersti 12, Linnakantselei 1. The module reads them live for exactly that reason.
Return
{source, page, body, term, ran, scoped, totals:{shown, reported, register}, rows, note}, each row {ref, date, title, type, body, url} with an ISO date and the document's own registration card.
totals.reportedis the register's own count over the whole match. State counts from it, never by tallyingrows.totals.registeris everything that body has ever registered, and it is there so the two can be compared — see Verify.scopedis the register's per-row echo: true when every row came back registered to the body you asked for.nullmeans there was nothing to check — no body was named, or the search matched nothing.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".
Limits
- A filter name this register does not know is ignored, not rejected, and the answer is that body's entire register at HTTP 200.
asutuz=12,org=12andregkpXX_algus=01.01.2026each come back with 46,064 and 54,691-row totals that look exactly like results. The module sends only verified names;totals.reported == totals.registeris what catches the platform changing under it. - A date it cannot parse is dropped the same way.
regkp_algus=2026-01-01— the ISO spelling — answers 200 with the whole register and nothing says the range was ignored. Only dd.mm.yyyy filters. command=quickis the front page's Kiirotsing and it ignoresasutus.command=quick&pealkiri=leping&asutus=12reports 46,064 — every body in Tallinn — wherecommand=searchwith the same pair reports hundreds, all of them that body's. A count from the wrong command is the whole city's, attributed to one district.
claim `command=quick` is the front page's Kiirotsing and it ignores `asutus`
GET https://dhs.tallinn.ee/atp/?c_tpl=1092&command=quick&pealkiri=leping&asutus=12
expect-status 200
# Five digits is the tell: the unscoped register answered 46,064 on 2026-08-02
# while Haabersti alone answered 487. If quick ever starts honouring asutus,
# this drops to hundreds and the claim fails, which is the point.
expect /Kokku leiti \d{5} dokumen[dt]/
# The control is command=search on the identical pair. It must fail the digit
# bound — that is the half proving the register still scopes at all, rather
# than the whole thing having shrunk below ten thousand documents.
control GET https://dhs.tallinn.ee/atp/?c_tpl=1092&command=search&pealkiri=leping&asutus=12
- The other direction fails silently too: a value the register does not recognise gives zero rather than the whole register.
suund=zzz, anasutusid that no longer exists, and droppingcommandaltogether all answerKokku leiti 0 dokumenti— a zero that reads as "this body has nothing on file". The module validates those before sending. - A multi-word title search is empty by default.
pealkiri_meetodis 0 = fraas, 1 = kõik sõnad, 2 = üks sõnadest, and the register's own default is 0:pealkiri=raie lubamatches 0 as a phrase, 5 as all words and 56 as any word. The module defaults toallfor that reason. - Restricted documents are published as registration data only — title, number, date and type are public, the content is not. Private people appear as initials or not at all.
- A body's register is large (Haabersti 54,691 rows, Kesklinn 54,921), so a wide term is slow rather than useful. Deep pages take about 2.5 s.
Verify
A search only succeeded if ran is true, which means the response carried the register's own count line: Kokku leiti <N> dokumenti, or 1 dokument for a single hit — the singular drops the final i, and matching only the plural reports every one-hit search as a search that never ran. Then:
scoped: falsemeans the rows came back registered to other bodies, so theasutusfilter was dropped and the count is the whole city's. Do not attribute it.totals.reported == totals.registermeans the register handed back everything that body has. Either the filter was ignored, or the term is in every title it holds — check a row before stating either.reported: 0withran: trueis a real, empty result for this register. Before calling it "no such document": the council's acts are in TEELE, and a Tallinn matter is often registered by the city office that handles it rather than by the district.- Never name a document without the
urlfrom its own row.
Known-good acceptance check, run against the live register by scripts/check-sources.mjs (issue #82). The counter is the same search with the asutus filter misspelt — HTTP 200, rows attached, and the count is every Tallinn body's:
GET https://dhs.tallinn.ee/atp/?c_tpl=1092&command=search&asutus=12&pealkiri=leping&pealkiri_meetod=1&page=1
expect /Kokku leiti \d{1,4} dokumen[dt]/
expect /<span class="label">Asutus<\/span>Haabersti Linnaosa Valitsus/
# 1,099 of Haabersti's 54,691 documents on 2026-08-02, the first being
# "Lepingu ülesütlemisavaldus". The counter misspells the filter as `asutuz`:
# it answers 46,064 — five digits, every body in the register — with rows from
# Tallinna Kultuuri- ja Spordiamet and others at the top, so it fails both
# assertions. If it ever satisfies them, a dropped scope has become
# indistinguishable from a search that ran.
counter GET https://dhs.tallinn.ee/atp/?c_tpl=1092&command=search&asutuz=12&pealkiri=leping&pealkiri_meetod=1&page=1
Module API
tallinnDocuments(body, term, {limit, match, from, to, direction}) -> {source, page, body, term, ran, scoped, totals, rows, note}