--- name: tallinn-council-documents description: Query Tallinn's TEELE JSON API for adopted council legal acts, council drafts, and document details. module: teele.mjs execution: get --- # Tallinn TEELE Documents Use this source for Tallinn City Council regulations, resolutions, drafts, and proceeding metadata. TEELE supersedes the broken `oigusaktid.tallinn.ee` wrapper for these records. **This is not Tallinn's document register.** TEELE holds the acts the council ADOPTED. The letters, teabenõuded, käskkirjad and contracts of the city's bodies — the eight district governments, Tallinna Linnavalitsus, the city offices and their schools — are a separate system at `dhs.tallinn.ee/atp`, with its own guide, `tallinn-document-register`. Asking here for a body's correspondence returns a clean, empty-looking answer, and that emptiness is a claim about what a public body has on file (issue #190). ## API Base: `https://teele.tallinn.ee/api` - System settings: `GET /systemSettings` - Search: `GET /documents` - Detail: `GET /documents/{id}?lang=EE` - Browser detail: `https://teele.tallinn.ee/documents/{id}/view` Read `COUNCILUNIT` from `/systemSettings` rather than hard-coding the current unit ID. Encode arrays as repeated query keys. **`teele.tallinn.ee` is a single-page app.** Every path — a document id that does not exist, a path that never existed — answers HTTP 200 with the same 624-byte shell, so **fetching a `/documents/{id}/view` URL can never confirm it is right** (the same property as `tpr.tallinn.ee`, issue #114). The API is the only thing that can: `GET /api/documents/999999999` answers **404 `{"error":"Dokumenti ei leitud"}`**. Cite a browser link only for an id the API returned. ```probe-limit claim `GET /api/documents/999999999` answers **404 `{"error":"Dokumenti ei leitud"}`** GET https://teele.tallinn.ee/documents/999999999/view expect-status 200 expect TEELE # Both halves of the paragraph, in order: the browser path hands back the shell # with HTTP 200 for an id that cannot exist, and the API says no properly. The # day the shell starts 404ing, the first step fails and the "can never confirm # it is right" rule is the thing to re-read. GET https://teele.tallinn.ee/api/documents/999999999 expect-status 404 expect /Dokumenti ei leitud/ # The control is a call every TEELE run makes anyway. Without it, an API that # had started answering 404 to everything would look like a working register # that simply does not hold this id. control GET https://teele.tallinn.ee/api/systemSettings ``` ## Search contracts Common pagination: `page=1&pageSize=10&lang=EE`. A `pageSize` below 5 is silently served as 5. Free text (undocumented by the register, verified 2026-08-01): - `phrase=` — matches the title. `phrase=Veerenni` matched 317 documents. - `content=` — matches the document body. `content=Veerenni` matched 400 documents. - Both numbers are the `rowCount` of an HTTP 200 answer, re-checked 2026-08-02: neither filter is refused, and neither number is a status code. This used to be written as an arrow followed by the bare count, which reads as a rejection and keeps an agent off the body search. - `publishedAtFrom` / `publishedAtTo` — `yyyy-MM-dd`. Adopted council acts: - `documentTypes=RESOLUTION&documentTypes=REGULATION` - `publisherUnitId={COUNCILUNIT}` - `status=ACCEPTED` - `sortColumn=publishedAt&sortDirection=desc` Council drafts in active council proceedings: - same document types and publisher unit - `statuses=INCOUNCILPROCEEDING&statuses=WAITINGFORCOUNCILMEETING` - `sortColumn=documentSubmission.acceptedAt&sortDirection=asc` ## Output and limits Keep `id`, `title`, document type/status, `number`, `draftNumber`, `publishedAt`, `documentSubmission.acceptedAt`, submitters, publisher, and access-restriction flags. Fetch detail for enforcement/publication metadata and use the browser detail only for full rendered content or attachments. Respect `pageCount`; do not treat restricted documents as accessible merely because they appear in search results. **A filter name this API does not know is ignored, not rejected.** `title=Veerenni`, `documentTitle=Veerenni` and `year=2026` each answer HTTP 200 with `rowCount: 132954` — every document Tallinn has ever registered — and nothing in the response says the filter was dropped. So the count looks authoritative and describes the wrong set. Send only names verified to filter: `publisherUnitId`, `documentTypes`, `status`, `statuses`, `phrase`, `content`, `publishedAtFrom`, `publishedAtTo`, `page`, `pageSize`, `sortColumn`, `sortDirection`. State counts from the response's own `rowCount`, which covers the whole match, not by tallying the page of `results` you printed. ## Verification A valid search response contains `page`, `pageCount`, `rowCount`, and `results`; each result has an integer `id`, title, document type, and status. Known-good acceptance check, run against the live API by `scripts/check-sources.mjs` (issue #82). The counter is the same word sent under a filter name the API drops: it answers 200, with a JSON body, and a `rowCount` six digits long — a search that was never narrowed. ```probe GET https://teele.tallinn.ee/api/documents?page=1&pageSize=5&lang=EE&phrase=Veerenni expect /"rowCount":\d{1,4},/ expect /Veerenni/ # 317 on 2026-08-01, first row "Veerenni tn 20 kinnistu ja lähiala # detailplaneeringu vastuvõtmine Kesklinnas" (id 153204). The counter returns # rowCount 132954 — the whole register — because `title` is not a filter this # API has. If it ever satisfies the digit bound above, an ignored filter has # stopped being distinguishable from one that applied. counter GET https://teele.tallinn.ee/api/documents?page=1&pageSize=5&lang=EE&title=Veerenni ``` ## Module API councilDocuments({phrase, content, types, from, to, status, page, pageSize}) -> {source, page, register, filters, totals, rows, note} councilDocument(id) -> {source, page, id, title, type, status, number, draftNumber, publishedAt, enforcedAt, acceptedAt, submitter, institution, publisher, access, note}