--- name: construction-register description: Query Estonia's Construction Register (EHR) public JSON endpoints for building search results and detailed building records. module: ehr.mjs execution: post --- # Construction Register (EHR) ## Vetted module (use this first) `ehr.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 { buildingsAt, buildingDetails } from "./ehr.mjs"; const r = await buildingsAt("Soo 4, Tallinn"); show({ fuzzyHits: r.total, realMatches: r.matched, source: r.source, page: r.page, note: r.note }); show(r.rows); if (r.rows.length) { const d = await buildingDetails(r.rows[0].ehrCode); show({ ehr: d.ehitiseAndmed.ehrKood, nimetus: d.ehitiseAndmed.nimetus, aadress: d.ehitiseAndmed.taisaadress }); show(d.ehitiseTehnilisedNaitajad, 800); // pick the sub-objects you need; the record is large } ``` - `buildingsAt(address)` runs the search and then filters EHR's fuzzy results down to addresses that really contain every word of the query, matching within a single address component. Without it "Soo 4, Tallinn" returns 86 hits led by "Sookaskede pst 4", and a linear structure listing hundreds of addresses can match "Soo" from one and "4" from another. It pages until something matches. - **When the question names a place rather than a building, resolve it with `address-search` first.** That returns the EHR code outright, so `buildingDetails(ehrCode)` can be called directly — no fuzzy search, and no chance of answering about the same street number in another town. Use `buildingsAt()` when you have only text and no resolved address. - `buildingDetails(ehrCode)` verifies the returned record is the one requested and returns the nested `ehitis` payload — print selected fields, never the whole object. The payload also carries `source` (the detail URL fetched) and `page`. - **The two URLs you may cite are `source` and `page`, and there is no third.** `source` is the API endpoint; `page` is the register's public search UI, the only EHR page a citizen can open. `ehr.ee` does redirect here (301 to the UI root, checked 2026-07-28) and is still not a citation: it is a page no run opened, and the same habit that writes it also writes `/ui/ehr/v1/building/` deep links, which cannot be checked at all — the UI answers 200 with the same shell for every path. - `note` says what this API does not hold — owners, mortgages, permit files. Report that, and name e-kinnistusraamat for ownership instead of linking a register this run did not query. - Also exported: `searchBuildings({address, name, page, pageSize})` for the raw unfiltered search. The endpoints below are the fallback if a module call throws. ## Access - Search API: `POST https://livekluster.ehr.ee/api/building/v2/buildingSearchPageable` - Detail API: `GET https://livekluster.ehr.ee/api/building/v3/buildingData` - Public search UI: `https://livekluster.ehr.ee/ui/ehr/v1/detailsearch/BUILDINGS_SEARCH` - Response: JSON; no login is required for these two API calls. ## Search Send JSON with at least one useful filter plus one-based pagination: ```json { "buildingName": "raekoda", "page": 1, "pageSize": 10 } ``` Supported public UI filters map directly to these payload fields: - `buildingLocation`: address, EHR code, or cadastral identifier - `buildingName` - `buildingType`: array containing `H` (building) or `R` (structure) - `buildingStates`: array of classifier codes - `purposeOfUse` and `ocurringPurposeOfUse`: arrays of purpose IDs - `lastRegisteredDocumentType`, `ownershipType`, `heritage` - `firstUseDate`: object with optional `from` and `to` year strings - `firstUseMissing`: boolean - `page`, `pageSize`, optional `sortField`, `sortDir` The response contains `page`, `pageSize`, `total`, and `data`. Keep each result's `ehrCode`, `buildingId`, addresses, name, type, state, purpose, first-use year, dimensions, ownership type, latest document metadata, and geometry. ## Detail After selecting an `ehrCode`, request: ```text GET https://livekluster.ehr.ee/api/building/v3/buildingData?ehr_code=&json=true ``` The detail response is nested under `ehitis` and includes basic data, addresses, cadastral units, geometry, uses, dimensions, technical systems, and energy labels when present. ## Limits - **The building search is a form POST**, so an agent that can only issue GET requests cannot run it; the detail call above is a plain GET, so resolve the place with `address-search` first and query the EHR code it returns directly. - Owner search uses an authenticated endpoint and is not part of this public contract. - Public access to some attached documents is temporarily restricted; do not promise document-file access from the building APIs. - Geometry coordinates are in the register's source coordinate system; do not label them WGS84 without conversion. ## Verify Require a numeric `total`, a non-empty `data` array, and `ehrCode`, `buildingId`, `buildingAddress`, and `buildingState` in a search result. For detail calls, require `ehitis.ehitiseAndmed.ehrKood` to equal the requested code. ## Module API buildingsAt(address) -> {source, page, note, total, matched, rows} buildingDetails(ehrCode)