---
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}