--- name: land-register description: Free e-kinnistusraamat index search: register part, cadastral unit, address, area, KÜ code, land use. module: kinnistusraamat.mjs execution: get --- # Estonian Land Register index (e-kinnistusraamat) The free layer is an **index**, not an extract: it resolves an address or cadastral unit to a register part and its basic facts. Owner, mortgages, servitudes and notations are paid and login-only — read `## Limits` before promising an encumbrance check. ## Vetted module (use this first) `kinnistusraamat.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 { landRegisterSearch } from "./kinnistusraamat.mjs"; const r = await landRegisterSearch("Vabriku tn 12, Tallinn"); show({ kokku: r.total, vasteid: r.matched, truncated: r.truncated, page: r.page, note: r.note }); show(r.rows); // the rows that really match the query show(r.nearby.map((n) => n.aadress)); // the street neighbours the fuzzy search also returned ``` - `landRegisterSearch(term)` takes an address, a katastritunnus (`78401:101:5214`) or a registriosa number (`2349701`). The register's address search is fuzzy at street level — "Vabriku tn 12, Tallinn" returns 15 rows led by Vabriku 31 — so the module splits them: `rows` match every word of the query, `nearby` is everything else. **A street neighbour presented as the answer looks exactly like a correct answer**, so read `matched` before quoting a number. - **`total` is the register's own count and `rows` may be fewer.** The result page renders 25 rows and reports its count separately (29 against 25 rendered, for one Vabriku 31 query); `truncated` says when that happened. State counts from `total`. - **The register throttles anonymous searches with a CAPTCHA** — measured at the fourth search in a row, clearing after about half a minute. The module waits it out and retries, and if it still cannot get through it says so. A captcha page is not "this address does not exist", and nothing may report it as one. - `note` carries the boundary this recipe exists for: no owner, no mortgage, no servitude, no notation. The answer has to say that they were not retrieved. - `source` and `page` are the same URL here — the search result opens in a browser without a login — and it is the only link a result entitles you to add. The endpoints below are the fallback if a module call throws. ## Access Application: `https://kinnistusraamat.rik.ee/` (ASP.NET) — the app shell, which opens on a blank search form. Exactly one endpoint answers anonymously, with no payment: ```text GET https://kinnistusraamat.rik.ee/PealeheOtsinguTulemus.aspx?term={query}&kehtetuOmanik=false&oigustatudIsik=false&otsi=1 ``` Send `Accept: text/html` only to override a client that asks for JSON by default, as `get()` does — a fetch sending no `Accept` at all returned the same page with `main-result-table` and `fields_HiddenResCount` present (verified 2026-08-03 on `78401:101:5214`), so an agent that cannot set headers can still run this. `term` accepts an address (`Vabriku tn 12, Tallinn`), a cadastral unit number (`78401:101:5214`) or a register part number (`2349701`). Address matching is fuzzy at street level, so a street query returns the neighbours too — pick the row whose address matches. **Search by cadastral unit whenever you can.** `address-search` turns a free-text address into one, and that number returns a single exact row where the address text returns the street. It also settles which town was meant before this register is asked — the address query cannot, and answers about the wrong Vabriku 12 look identical to right ones. Everything else on that host requires TARA login (ID-card / Mobiil-ID / Smart-ID). `GET .../KRavalikTeated.aspx` redirects to `Login.aspx?ReturnUrl=…`; `SearchResultPrint.aspx` and the `default.aspx?LoadToFile=true…` export both need the logged-in session and return an empty page or a redirect without it. ## Retrieve Parse ``. Data columns, in order: **Kinnistu nr, Kinnistu liik** (`Kinnisasi`, `Korteriomand`, `Hoonestusõigus`, …), **Katastritunnus, Katastriüksuse aadress, Pindala, Eriomand** (count of eriomand units, or `*`). The row immediately after each result carries **Korteriühistu registrikood** and **Sihtotstarve**. The hidden input `fields_HiddenResCount` holds the match count. ```js import { get, show } from "./kratt.mjs"; const cell = (h) => h.replace(/<[^>]+>/g, " ").replace(/ /g, " ").replace(/\s+/g, " ").trim(); const term = "Vabriku tn 12, Tallinn"; const url = "https://kinnistusraamat.rik.ee/PealeheOtsinguTulemus.aspx?term=" + encodeURIComponent(term) + "&kehtetuOmanik=false&oigustatudIsik=false&otsi=1"; const p = await get(url, { headers: { Accept: "text/html" } }); const tbl = p.match(/
/)?.[0] ?? ""; const rows = [...tbl.matchAll(//g)] .map((m) => [...m[0].matchAll(//g)].map((c) => cell(c[0]))) .filter((r) => /^\d+$/.test(r[1] ?? "")) // data rows start with the register part number .map((r) => ({ kinnistuNr: r[1], liik: r[2], katastritunnus: r[3], aadress: r[4], pindala: r[5] })); show({ source: url, kokku: p.match(/HiddenResCount" value="(\d+)"/)?.[1], rows }, 3000); ``` Zero results render "Otsingutulemused puuduvad.", no `main-result-table`, and `HiddenResCount` = 0. ## Return Preserve `Kinnistu nr`, `Kinnistu liik`, `Katastritunnus`, the full `Katastriüksuse aadress`, `Pindala`, eriomand count, `Korteriühistu registrikood`, `Sihtotstarve`, the query term, the match count and the URL fetched. State explicitly that ownership and encumbrances were not retrieved. **The `PealeheOtsinguTulemus.aspx?term=…` URL you fetched IS the citation.** It needs no login and renders the same result table in a browser (verified anonymously 2026-07-28), so it is both the source and the page the citizen can open. The application root above is a pointer, not a source: citing it in place of the URL that carries the row tells the reader the answer came from a blank form. ## Where the rest of a property check comes from These are recipe names, not links. To point the citizen at one, query it and cite what came back; a register this run did not open may be NAMED in the answer but never linked — a domain built from a register's name is a guess that looks like a source. - Cadastral geometry, restrictions and map layers: `geospatial-open-data`. The result page also builds a Maa-amet link: `https://xgis.maaamet.ee/xgis2/page/app/maainfo?showsearchlayer=1&hide=true&searchid=FADS0703&ORIG_TUNNUS={katastritunnus}`. - Buildings, building permits and kasutusluba on the plot: `construction-register`. - The korteriühistu behind a `Korteriühistu registrikood`: `business-register-open-data`. - Detail plans and public-display windows affecting the plot: `planning-decisions`. - If the object is on a forced-sale auction, its **III and IV jagu encumbrances are published in full for free** in the auction notice — see `bailiff-proceedings`. - Kinnistusraamatu teated in Ametlikud Teadaanded: main type `kinnisturaamat`, subtypes `kinnistusraamatu-registriosa-avamine`, `kinnistusraamatust-eelmarke-kustutamise-noue`, `kohtu-kinnistusosakonna-maaruse-kattetoimetamine` — via `official-notices`. - Paid-layer prices, if the citizen asks what a full check costs: `https://www.rik.ee/et/e-kinnistusraamat/e-kinnistusraamatu-portaal/teenuste-hinnad` (fetch it; do not quote from memory). ## Limits - The free layer answers "which register part and cadastral unit is this address, how big is it, what is it for" — it contains **no owner, mortgage, servitude, notation or document data**, so it cannot complete an encumbrance or ownership check on its own. - The paid sections (I jagu, II jagu owner, III+IV jagu encumbrances, whole register part) need a login or a card payment. Do not buy data; tell the citizen the price and the route. - Searching by a private person's name or isikukood is refused by the register itself ("Eraisiku nime või isikukoodi järgi kinnistuid otsida ei saa"), so "what property does X own" is not answerable here. - Viewing personal data in the paid layer is logged to the subject's eesti.ee andmejälgija for a year — worth telling a citizen who is about to look someone up. ## Verify - A retrieval counts only when `
` is present **and** at least one row begins with a numeric `Kinnistu nr`, **and** `fields_HiddenResCount` parsed as an integer. The `Avaleht.aspx` shell, a `Login.aspx` redirect, and a table with only its header row are all NOT data. - Use a row only if `Kinnistu nr` and `Katastritunnus` are both present. - Never report an owner, hüpoteek, servituut or keelumärge from this endpoint: it does not return them. ## Module API landRegisterSearch(term) -> {source, page, term, total, matched, truncated, rows, nearby, note}