--- name: economic-activities-register-mtr description: Search Estonia's legacy Economic Activities Register (MTR) for public operators, activity notices, and licences. module: mtr.mjs execution: code --- # Economic Activities Register (MTR) ## Vetted module (use this first) `mtr.mjs` ships in https://kodanikukratt.ee/kratt-agent.tar.gz (plain ESM, Node 22+) and is re-verified against the live source: ```js import { show } from "./kratt.mjs"; import { mtrOperators, mtrOperator } from "./mtr.mjs"; const r = await mtrOperators({ nimi: "Tallinna Vesi" }); // or { registrikood: "10257326" } show({ leiti: r.totals.found, read: r.rows, note: r.note }); const o = await mtrOperator(r.rows[0].id); show({ nimi: o.nimi, kood: o.registrikood, kokku: o.totals }); show(o.notices); // majandustegevusteated — what the operator DECLARED show(o.licences); // tegevusload — what the state GRANTED ``` - The cookie + CSRF handshake is done for you: the form GET issues an `mtr` session and a `_csrf_token` that only counts with it, the POST stores the filter, and the results are read from a plain GET that **without the cookie serves an unfiltered listing**. The module also requires the register to echo the filter under "Kasutatud filtrid" and throws if it does not — an unfiltered listing is HTTP 200 and looks like a very successful search. - **A majandustegevusteade is not a tegevusluba.** Both tables have byte-identical column headers, so they are told apart by their section heading; a teade is something the operator declared, a luba is something the state granted after checking. - **The register contradicts itself on real rows.** AS Tallinna Vesi's `SLM21/1633` ends 12.04.2023 and is still marked `Kehtiv: Jah` (2026-08-01). `inForce` is the register's own column, `endedOn` is what its own end date implies, and `conflicting: true` marks the disagreement — report both rather than resolving it. - Each notice and licence carries its own `page` (`/taotluse_tulemus/`, public, checked live), and each operator row carries the operator's `page`. `source` on a search is session-scoped and will not reproduce for anyone else, so cite `page`. - **MTR is no longer exhaustive.** Since 25 June 2025 notices and licences are moving to `tarvik.ttja.ee`, so something missing here may have moved rather than lapsed — say what MTR holds and name tarvik.ttja.ee as the other place to check, never "they have no licence". - The e-mail on an operator page is obfuscated in the markup, so `contacts.email` comes back null rather than as a guess. The endpoints below are the fallback if a module call throws. ## Access - Operator search form: `GET https://mtr.ttja.ee/juriidiline_isik?m=96` - Form submission: `POST https://mtr.ttja.ee/juriidiline_isik/filter/action` - Response: server-rendered HTML using a session cookie and CSRF token; no login is required for public searches. ## Retrieve 1. Create one cookie-preserving HTTP session and fetch the search form. 2. Extract `juriidiline_isik_filters[_csrf_token]`. 3. Submit the form in the same session. Useful fields are: - `juriidiline_isik_filters[nimi][text]` - `juriidiline_isik_filters[registrikood][text]` - `juriidiline_isik_filters[tegevusala_id]` - `juriidiline_isik_filters[tegevusala_tyyp]`: `1` for notices or `2` for licences - `juriidiline_isik_filters[valjund_valjad][]`: request `nimi`, `registrikood`, and any other output columns needed 4. Follow the redirect to `/juriidiline_isik` and parse rows from `div.sf_admin_list`. 5. Follow each row's `/juriidiline_isik/` link. The detail page contains `Majandustegevusteated` and `Tegevusload` tables with validity and activity fields. Preserve the operator name, registration code, MTR record number, activity, validity dates, current/archive state, and detail URL. ## Limits - Since 25 June 2025, notices and licences are being moved from MTR to `https://tarvik.ttja.ee/`; MTR alone is no longer guaranteed to be exhaustive for current activity. - The public form is stateful. A POST without the cookie and current CSRF token will not reproduce the search. **So this is one of the few sources plain requests cannot search at all**: the session has to be carried from the form GET into the POST and on into the result GET, and an agent that can only fetch URLs gets the search form back — which is our limit, not an empty register, and must be reported that way rather than as "no such operator". - Use the dedicated notice (`/majandustegevusteade`) or licence (`/tegevusluba`) forms when the user asks for record-first rather than operator-first results; they use the same session/CSRF pattern. ## Verify Require the submitted filter value to reappear under `Kasutatud filtrid`, at least one result row containing both name and registration code, and a working `/juriidiline_isik/` detail link. ## Module API mtrOperators({nimi, registrikood, fie}) -> {source, page, filter, ran, totals, rows, note} mtrOperator(id) -> {source, page, nimi, registrikood, contacts, addresses, totals, notices, licences, note}