--- name: consumer-technical-regulator-decisions description: TTJA's public register of Consumer Disputes Committee decisions - which trader was complained about, how it was decided, and the full reasoning. module: tarbijavaidlused.mjs execution: get --- # Consumer Disputes Committee Decisions 7,935 decisions on 2026-08-02. This is where a citizen finds out whether a trader has been before the komisjon, and what the komisjon decided when someone else brought the same complaint. ## Access - JSON: `GET https://jvis.ttja.ee/modules/tarbijavaidluskomisjoni-otsused/avalik?_wbbdl=1` with `X-Requested-With: XMLHttpRequest` and `Accept: application/json`. Public, no login. - Decision PDFs: `https://jvis.ttja.ee/modules/media/media/download/`. - The response is `{result, msg, total, items}`. `result: "success"` with `total: 0` is a real no-match; **`result: "error"` is a rejected query** and must never be reported as an empty register. ## Retrieve `consumerDisputes({company, from, to, docNr, outcome, text, limit, offset})`, then `consumerDecisionText(row)` for the reasoning. Each of those filters was sent alone on 2026-08-02 and its count compared with the unfiltered 7,935: `company=Telia` 88, `outcome` for the consumer 5,384, `text=lennureis` 43, the window 2026-01-01..2026-07-31 586, an exact `docNr` 1. The date window was proved against rows that carry a date — Telia limited to 2020 returns five decisions, every one published in 2020. **`search[group_search]` is not a field.** It answers `result: "error"`, and the module refuses it along with any other name, because a rejected query here comes back looking like a register with nothing in it. Paging is `limit` and `offset`, which are this register's own — `page`, `start`, `per_page` and `length` are all rejected. Telia's 88 decisions come back as 50 + 38 with no overlap. ## Return Trader, publication date, decision number, outcome, committee, summary, PDF URL, the query, `totals.decisions` and retrieval time. A row's `summary` is an index blurb written for browsing; the reasoning is only in the PDF. ## Limits - **The rows carry no date unless the query names a company.** Verified 2026-08-02: `publishing_date` is present with `search[company]`, absent for the unfiltered listing, for an outcome filter alone and for a date window alone — while the date filter itself still works. Do not infer a date from the document number: its year segment is when the case was registered, and decisions are published later (a 2019 case decided in 2020 is in there). Re-query with `company`, or open the PDF, where the date is printed. - **The consumer is a private person.** The JSON names them next to the trader; the module does not return that name and an answer does not either. The trader is the party the public interest is in. TTJA's own published PDFs are already redacted this way — they print "Tarbija" with no name. - Committee members are named in both the JSON and the PDF. Name them only when the question is about the komisjon itself. - This register covers Consumer Disputes Committee decisions, not TTJA's wider supervision or enforcement. - A decision binds nobody by itself, and says so on its own face: a party that disagrees may take the same dispute to a maakohus. ## Verify - A real result is HTTP 200 with `result: "success"`, `total` present and `items` non-empty for a focused query. - The first `public_pdf_id` must download bytes beginning `%PDF-` before anything is quoted from it. - A document number that does not exist answers `result: "success"` with `total: 0` — the shape of a genuine no-match, and what distinguishes one from a rejected field name. **Without the two request headers this endpoint serves HTML, not JSON** — 18 KB of the register's own modal, HTTP 200. That is why there is no `probe` block here: the weekly check runs the module instead (`tarbijavaidlused:consumerDisputes+decisionText` in `scripts/check-sources.mjs`), which sends the headers, asserts the company filter narrows, and asserts an unverified field name is refused rather than passed on. ## Module API consumerDisputes({company, from, to, docNr, outcome, text, limit, offset}) -> {source, page, query, totals, rows, note} consumerDecisionText(rowOrDocumentNumber) -> {source, page, docNr, company, published, outcome, pages, chars, text, note}