--- name: forest-register description: Query the public Forest Register JSON API for properties, cadastral units, forest stands, inventory attributes, geometry, and proposed work. module: metsad.mjs execution: get --- # Forest Register ## Vetted module (use this first) `metsad.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 { forestStands, standDetails } from "./metsad.mjs"; const p = await forestStands({ kinnistuNr: 12345 }); show({ kokku: p.totals, note: p.note }); show(p.rows.slice(0, 5)); const s = await standDetails(p.rows[0].id); // the row's `id`, NOT eraldiseNr show({ maakond: s.maakond, vald: s.vald, korraldaja: s.korraldaja, raied: s.plannedWork }); ``` - `forestStands({katastriNr, kinnistuNr, yksuseNimi, kvartaliNr})` flattens the property tree and **counts and sums in code**: `totals.stands` and `totals.areaHa` are computed over every stand the search returned, so they stay right when only some rows are printed. - **County and municipality narrow a search; they are not a search.** `maakondKood`/`valdKood` alone return an empty list, which is not the same as "there is no forest there" — the module throws with that sentence rather than letting the empty list be read as an answer. - **A stand row carries no planned felling.** `tood` and `elemendid` come back empty from the search and are filled only by `standDetails(id)`, whose `plannedWork` is the proposed raie with `tooKood` and `kogusTm`. Answer "is a felling planned here" from there, never from a row. - **`id` is not `eraldiseNr` and not `yksusId`.** The register answers HTTP 400 `{"message":"Tehniline viga"}` for a `yksusId` — and for an `eraldiseNr`, and for any other number that is not a stand id (re-checked 2026-08-02; this guide said 403 until then). A bare "technical error" reads like the register being broken rather than the caller holding the wrong identifier, so the module rejects both by name first. ```probe-limit claim The register answers HTTP 400 `{"message":"Tehniline viga"}` for a `yksusId` GET https://register.metsad.ee/portaal/api/rest/eraldis/detail/198528 expect-status 400 expect /Tehniline viga/ # 198528 is the yksusId of the property `forestStands({kinnistuNr: 12345})` # returns; 1675494 is one of its stands. The control is the SAME route with a # real stand id, because a register answering 400 to everything would confirm # this claim on a day when standDetails was broken for every caller. control GET https://register.metsad.ee/portaal/api/rest/eraldis/detail/1675494 ``` - **`codeFields` are classification codes with no public code list** — every `klassifikaator*` path answers HTTP 302 to `auth/oauth2/authorization/govsso`, and following that redirect lands on the state authentication service's 200 HTML login page (`tara.ria.ee`) rather than an error, so a `get()` on one returns login markup that can pass for a result; the app's own bundles map each code to itself (re-checked 2026-08-02). Quote the code; do not translate it from memory. - `etapp: "AEGUNUD"` means the **inventory** has expired, not that the forest is gone. The endpoints below are the fallback if a module call throws. ## Access - Application: `https://register.metsad.ee/` - API base: `https://register.metsad.ee/portaal/api/rest/` - No authentication is required for the public calls below. ## Search Send `GET eraldis/puu` with one or more URL parameters: - `katastriNr`: cadastral identifier. - `kinnistuNr`: property number. - `yksuseNimi`: property name. - `kvartaliNr`: forest quarter number. - `maakondKood` and `valdKood`: optional location narrowing. At least one of `katastriNr`, `kinnistuNr`, `yksuseNimi`, or `kvartaliNr` is required; county or municipality alone is not a search. Example: ```text GET https://register.metsad.ee/portaal/api/rest/eraldis/puu?kinnistuNr=12345 ``` The response is a property tree: top-level units contain `alamYksused`, and cadastral sub-units contain `eraldised`. Each stand has an `id`, `katastriNr`, `eraldiseNr`, `pindala`, status, ownership label, and EPSG:3301 GeoJSON string. ## Detail Use the stand `id`, not its displayed stand number: ```text GET https://register.metsad.ee/portaal/api/rest/eraldis/detail/{id} ``` Return identifiers, property and cadastral data, inventory date, county/municipality, area, site and main-species codes, dimensions, age/composition elements, volume/increment, damage, special features, proposed work, ownership fields, and `alaGeoJson` as available. ## Limits - Code fields such as `kasvukohaKood`, `peapuuliik`, and `tooKood` are register classifications; do not guess labels when a code list has not been retrieved. - Dates are Unix epoch milliseconds in several fields. - `alaGeoJson` is a JSON-encoded string in EPSG:3301, not an already parsed WGS84 geometry. ## Verify Require a non-empty `eraldised` list, then require the detail response `id` to match the requested stand and preserve `katastriNr` plus `eraldiseNr`. ## Module API forestStands({katastriNr, kinnistuNr, yksuseNimi, kvartaliNr, maakondKood, valdKood}) -> {source, page, filter, totals, rows, note} standDetails(id) -> {source, page, id, katastriNr, eraldiseNr, maakond, vald, korraldaja, inventKp, plannedWork, elements, damages, codeFields, record, note}