Statistika, teated ja muud andmed
Statistikaamet
Ametlik statistika ajaridadena koos tabelite metoodikaga Statistikaameti liidesest.
Statistikaamet avaldab ametliku statistika andmebaasis andmed.stat.ee, kus üks näitaja on üks tabel ja tabelit saab lugeda aastate, kvartalite või kuude kaupa. Iga tabel katab oma perioodi ja tabeli vahetumine tähendab tavaliselt ka metoodika vahetumist: kaks tabelit sama näitaja eri aastate kohta on kaks eri aegrida ja Statistikaamet ütleb oma metoodikalehel, mis aastast alates arvud omavahel võrreldavad on. Kuudest või kvartalitest aastakeskmist ei tohi ise arvutada, sest nii tuleb arv, mida Statistikaamet ise ei avalda.
Mida su agent peab oskama
POSTVajab vormipäringut. Vähemalt üks vajalik päring on vormi saatmine (POST). Küpsiseid ega turvamärgiseid vahele ei jää, aga ainult lehti tõmbav agent seda päringut teha ei saa.
Vastused, mis on siit tulnud
- Kuidas on keskmine palk kümne aastaga muutunud?Kuidas on Eesti keskmine brutokuupalk viimase kümne aasta jooksul muutunud? Näita aastate kaupa.2026-08-03
Juhend ise
Inglise keeles, sest seda loeb mudel.
Statistics Estonia API
Vetted module (use this first)
stat.mjs ships in https://kodanikukratt.ee/kratt-agent.tar.gz (plain ESM, Node 22+) and is re-verified against the live source. It hides the PXWeb POST contract and flattens json-stat2 into printable rows.
There are thousands of tables and this guide names none of them. Find the one the question needs; do not recall an id.
The method
findTables(term)— search the catalogue by what the statistic is called. Every hit comes back with what it COVERS:periods, the period codesfirstandlast, andannualFrom/annualTo, the years it publishes as ANNUAL values.- Check the coverage against the period asked about, and check it on the right field.
annualFrom/annualToare numbers: compare them.first/lastare period codes like"2022Q4"and do not compare — a table whose quarters end in2022Q4may still publish annual values, andlast >= "2025"throws it away. A table that does not list the years you need is not an answer, and querying it for them returns HTTP 400. - A current table that starts too late is not the end of the search.
discontinued: truemeans the table is in the archive, not that it is wrong: it still answers for the years it covers, and earlier years usually exist nowhere else. Many archived quarterly or monthly tables also list bare YEARS, which the title does not say. - Match the granularity the question asks for. An annual figure comes from a table with an annual span; a table of months or quarters is a different statistic. Never average months or quarters into a year yourself — that publishes a number Statistikaamet does not, and the office's own annual value is usually not the mean of its quarters.
subPeriodnames a second dimension that splits the year (Kuu,Kvartal); a table that has one holds only months or quarters, reportsannualFrom: nullhowever annual its year list looks, and a query must name that dimension too. tableMeta(id)for the exact variable and value codes, thentableRows(id, {query}). Codes differ between tables measuring the same thing, so read the contract of every table you query.
claim A table that does not list the years you need is not an answer, and querying it for them returns HTTP 400
# IA001 (consumer price index) lists 1999 onwards, so 1990 is a year it will
# never hold. PXWeb reads the JSON body whatever Content-Type is sent, which is
# why this posts JSON under the probe runner's form encoding.
POST https://andmed.stat.ee/api/v1/en/stat/IA001
body {"query":[{"code":"Aasta","selection":{"filter":"item","values":["1990"]}}],"response":{"format":"json-stat2"}}
expect-status 400
expect /Bad Request/
# The control is the identical query for 1999, the table's own first year. It
# must come back 200 json-stat2: a 400 from a table that had been withdrawn
# would otherwise read as the coverage rule working.
control POST https://andmed.stat.ee/api/v1/en/stat/IA001
body {"query":[{"code":"Aasta","selection":{"filter":"item","values":["1999"]}}],"response":{"format":"json-stat2"}}
findTables also PRINTS a line of its own, starting [stat.mjs], when the annual years it found are split between the live tables and the archive. That line is about the question you are answering — read it before choosing tables.
A series that changes table changes methodology
Two tables covering different years of "the same" indicator are two series. Their overlap years normally disagree, because the statistic was remeasured — that is usually why the old table was closed.
tableMethodology(id) fetches what Statistikaamet itself publishes about it (ESMS section 15.2, "Ajaline võrreldavus"): comparableSince is the year from which the office says its own figures are comparable. Quote that document — never a break you inferred. Then, in the answer itself:
- Say that the methodology changed, name the year, and cite the ESMS page. An answer that lists both series without a word about the break is wrong even when every figure in it is right.
- Give all the years the question asked for, labelled by which series each came from.
- Never state a change, growth rate or trend measured across the join — not a year-on-year step over it, and not the total over the whole span. Both are artefacts of the remeasurement: they compare two different definitions and come out several points away from the truth.
- The reader still asked how much it changed, so answer that within each series ("X → Y over 2021-2025"). That is a real number and the cross-break one is not.
import { show } from "./kratt.mjs";
import { findTables, tableMeta, tableRows, tableMethodology } from "./stat.mjs";
// 1. Find the table by the statistic's name, and read what it covers.
const found = await findTables("keskmine brutokuupalk");
show({ note: found.note, total: found.total });
show(found.tables.map(({ id, discontinued, timeVariable, subPeriod, first, last, annualFrom, annualTo }) => ({ id, discontinued, timeVariable, subPeriod, first, last, annualFrom, annualTo })));
// 2. Pick tables by the YEARS they list — numbers, never the period codes.
// Whatever years the question needs, on both sides of the archive line.
const covers = (t, from, to) => t.annualFrom !== null && t.annualFrom <= from && t.annualTo >= to;
const t = found.tables.find((x) => covers(x, 2016, 2020)); // often an archived table
// 3. Read that table's own contract, then query it.
show((await tableMeta(t.id)).variables.map((v) => ({ code: v.code, values: v.values.slice(0, 5) })));
const { source, page, rows } = await tableRows(t.id, { query: { [t.timeVariable]: ["2016", "2017"] } });
show({ source, page, rows }); // `source` is the URL queried; `page` is where a person reads it
// 4. Taking the remaining years from a second table? Check the break before joining them,
// then say in the answer what it says and give the growth within each series, not across.
show(await tableMethodology(t.id));
findTables(term, {limit})returns up to 30 hits (60 max), split between live and archived tables, withtotalsaying how many matched. Zero hits means the WORD is wrong, not that the source is down.tableRows(tableId, {query})POSTs the query and returns{source, page, rows}where rows are{<dimension label>: value label, …, value}.queryis a plain object like{Aasta: ["2025"]}; omitted dimensions come back aggregated by the table's own default.- Link
page, and never build a stat.ee path yourself.pageishttps://andmed.stat.ee/<lang>/stat/<tableId>, which PXWeb redirects to the table's own page. tableMeta(tableId)returns the variable contract. Variable codes may be non-ASCII (Vanuserühm) and must be sent exactly as returned.- Also exported:
browse(path)(walk the folder tree),queryTable()(raw json-stat2) andtoRows(dataset). - Confirm units, reference period and suppression markers before analysis.
The endpoints below are the fallback if a module call throws.
Access
Public PXWeb JSON API. No authentication. Use https://andmed.stat.ee/api/v1/en/stat for English or replace en with et for Estonian.
Retrieve
- Search for a table with
GET /api/v1/en/stat?query=<term>; each hit is{id, path, title, published}and apathunderLepetatud_tabelidis an archived table.GET /api/v1/en/statwith no query lists the top folders; descend by appending returned folder IDs. - Fetch a table contract with
GET /api/v1/en/stat/{TABLE_ID}. It returnstitleandvariables; use each variable'scodeand allowedvaluesin the query. The time variable'svaluesare exactly what the table covers. - POST the query to the same table URL:
{
"query": [
{"code": "Aasta", "selection": {"filter": "item", "values": ["2025"]}}
],
"response": {"format": "json-stat2"}
}
- Request with
Content-Type: application/json. Usecsv,json-stat2, or another format accepted by the table service. - When methodology matters,
tableMethodology(TABLE_ID)walks the published chain: search for the id to get itspath, then read the table's own web page athttps://andmed.stat.ee/en/stat/<path with / replaced by __, leading __ stripped>/<ID>, which linksesms-metadata/<n>. That number addresses the ESMS document athttps://www.stat.ee/en/find-statistics/methodology-and-quality/esms-metadata/<n>— in Estonian the link readsesms-metaandmed/<n>and the document lives athttps://www.stat.ee/et/avasta-statistikat/metoodika-ja-kvaliteet/esms-metaandmed/<n>. Section 15.2 is comparability over time. Cite that URL when you state a methodology break.
Working sample: GET https://andmed.stat.ee/api/v1/en/stat/IA001, then POST the payload above. The response is JSON-stat 2 with table ID IA001, year 2025, and a numeric value.
Return
Preserve the table ID, source and update metadata, dimension codes and labels, selected values, units/decimals, observations, exact POST payload, and retrieval time. When an answer spans two tables, say which years came from which.
Limits
- The figures come out of a POST, and only a POST. A GET on the table URL hands back the variable contract, never the observations, so an agent that can only issue GET requests can find a table here and describe what it covers but cannot read a value out of it — that is a limit on what we can retrieve for such a reader, not on the service. The query POST is stateless: no cookie, no token, no prior request, and the body is accepted under either a JSON or a form content type (checked 2026-08-02).
- The API root itself returns 404; include the language and database path.
- Table IDs are not enough to construct a query: always read the current variable contract first.
- Confirm units, seasonal adjustment, reference period, and suppression markers before analysis.
- Variable codes may contain non-ASCII characters (e.g.
Vanuserühm); send them exactly as returned by the table contract. - Dimension values are codes too, and the response echoes their labels instead — send
"1", not"Kokku", or the query 400s. tableRows(id, {...})silently returns the WHOLE table if the filter is not nested underquery. It must betableRows(id, { query: {...} }); the wrong shape produces no error, just every row.- Discontinued tables still return contracts and data. Check the periods, not the title: a table titled "(2002–2022, KVARTALID)" can list bare years as well as quarters.
- The reverse trap is the older tables that split time across TWO dimensions — a year variable holding bare years, plus
KuuorKvartal. Every cell is then a month or a quarter and the table publishes no annual value at all.subPeriodnames that dimension andannualFromisnullfor such a table; a query must select from both. - Property prices: per-transaction data is not published anywhere public (
tehingud.maaamet.eeno longer resolves and the current Maa-amet service is behind auth). A wage-versus-property question is answered from the published dwelling price index and the wage tables — search for both.
Verify
Require HTTP 200 JSON, variables in the table contract, and matching id, dimension, size, and value fields in a JSON-stat response. A portal HTML page or API-base 404 is not successful retrieval. A search that matches nothing returns [], so an empty result is a real answer about the term, not a failure.
Module API
findTables(query) -> {source, total, shown, truncated, note, tables} tableRows(tableId, {query}) -> {source, page, rows} tableMeta(tableId) -> {title, variables} tableMethodology(tableId) -> {source, page, esmsId, comparableSince, comparability, note}