What the project is
hinnaguru.ee is a directory of service prices and providers in Estonia. Someone arrives with the question "what does a move cost and who should I hire" and gets the answer on a single page: real prices from company price lists, a range and a median, and alongside them a provider list carrying data from state registers.
The difference from a conventional directory comes down to one thing: companies do not pay to be listed, and what is known about them is not their own account of themselves. Years in business, headcount, turnover in that field of activity and any tax debt come from the open Business Register and Tax Board extracts, with a date and a source next to every value. So far 66 services are covered, with 1,096 companies listed and 3,487 price entries.
Let me state the limits up front: the site has only just opened to indexing. I have no traffic, no rankings and no enquiries to show, and I am not going to pretend otherwise. This article is about how the project is built and what surfaced in the data, not about results.
Where it came from: people do not want to fill in forms
My previous project in this niche was a request marketplace: the user describes a job, companies respond. Almost nobody submitted a request. The post-mortem surfaced an uncomfortable finding — the audience was not there for brokerage. It was there for two things: to understand what the job should cost, and to get the contractor's contact details. The form between those two steps was not a service but an obstacle.
Hence the central architectural decision behind hinnaguru.ee: there are no enquiry forms at all. Company contacts are open, the link to the company's own site is direct, and the project does not place itself in the middle. That deliberately cuts off the most obvious way to monetise — selling leads — in exchange for the page honestly answering the question the visitor arrived with.
The lesson generalises beyond directories: when users do not complete the action you want, the problem is not always the button and not always conversion mechanics. Sometimes the action on offer is simply not the one they came for. It is the same conversation as the gap between traffic and enquiries, except the answer here was "remove the form", not "redesign the form".
What Estonian open data actually gives you
This is the most underused part. Estonia publishes machine-readable extracts for free under an open licence, and hardly anyone builds on them.
| Source | Licence | What it gives |
|---|---|---|
| Business Register open data | CC BY 4.0 | Name, registry code, VAT number, registration date, address, fields of activity, headcount, turnover broken down by field of activity, annual reports |
| Tax and Customs Board debtor list | CC BY-SA 3.0 | Outstanding tax debt, updated daily |
| The companies' own websites | — | Price lists, service composition, identifiers for matching against the register |
The project database holds the register in full — 376,043 legal entities. The register refreshes weekly, the debtor list daily.
The key row in that table is turnover broken down by field of activity. That single field is what turns a directory from another list of phone numbers into a tool. Total revenue answers how big a company is in general, not whether it does the thing you came for and at what scale.
The main finding: Google's top 10 is not the niche's shortlist
I ran the first service — moving — through the pipeline by hand, before the pipeline was even written, specifically to see whether the data would diverge from search results. It did, and more sharply than I expected.
What the data shows and search does not:
- A company with €1.3M in revenue does €86,000 of it in this niche — 6.6%. It is a haulage firm that handles moves as a sideline. In search results it looks like a major player; within the niche it is smaller than a small firm doing €160,000 of moves and nothing else.
- A company with €1.4M of turnover specifically in moving appeared on none of the first five pages of results. It was found by back-filling from the register by activity code. Weak site, no SEO — as far as Google is concerned it barely exists.
- A company showing positive reviews in search results carried a five-figure tax debt. Nothing in the results hints at it.
- A company registered a year earlier, with €17,700 in revenue, sat in the results beside twenty-year-old firms.
The conclusion I take from this as an SEO: search ranks websites, not businesses. That is obvious as a sentence, but putting two columns side by side — search position and niche turnover — shows how loosely they are coupled. For a strong company with a weak site that is good news: the competitors above you often hold those positions not through scale but because somebody worked on their website. What that gap looks like from inside a niche is something I showed in the casino affiliate niche teardown — the same picture by different means.
What turned out harder than the spec assumed
The honest part. On paper the pipeline looked simple: take the search results, parse the sites, match to the register, collect prices. The very first manual run surfaced four things the spec did not contain.
1. Matching a website to a legal entity is twice as hard as it looks. The registry code on the site — "the most reliable signal" — was present on 5 sites out of 10. The VAT number added three more, including the only company with a proper price list. Total: 8 of 10. Two sites gave neither.
2. Site networks turned out to be routine. One company had four separate domains for a single service; two others had two each. Without deduplicating by registry code the list would have been twice as long and twice as useless, with the same company occupying half a page under different names.
3. Two legal entities on one website. One site carried two registry codes — different companies, different counties, different founding years. Taking whichever came first would have meant attributing data to the wrong one.
4. Prices in identical units are not comparable. Six rows all read €/hour, but €25/h is one worker with no vehicle and €68/h is a worker with a 14 m³ van. An average across that set lies in both directions. Service composition had to become part of the model: today the moving page calculates a median separately per composition — "van up to 15 m³ + 1 worker" and so on — and prices with unstated composition are flagged as non-comparable and excluded from the median. Of 34 companies, 13 publish prices in any form.
One small tactic that paid off unexpectedly well: domains for register-sourced companies were found not by paid search queries but by trying obvious variations of the company name and verifying the registry code on the site itself. Four hits out of fifteen attempts, at no cost.
Why this is programmatic SEO, not page spinning
The 66 service pages are assembled from a database through one template — formally exactly what gets called programmatic SEO. The difference from the usual meaning lies in the data source: the pages are not built by permuting words in a template but from data competitors do not have. A service page exists because a register, price lists and niche statistics have been gathered behind it, not because there was one more name on a list.
That produces rules I hold strictly:
- A publication threshold. Fewer than three providers and the page does not ship: it fails to answer "who should I hire".
- A section appears when there is data for it. An empty block tells the reader the project does not know the thing it set out to explain.
- Never invent. No price means the page says there is none. If the legal entity could not be identified, no link is made. Attributing someone else's data to a company is worse than showing no data at all.
- No value judgements. "Trading since 2011" is a fact. "Reliable company" is a judgement that eventually arrives as a complaint.
Where that line runs and what Google actually penalises in generated pages is covered separately in the article on programmatic SEO. The short version: scale itself is not the problem, scale without data is.
Technically it is Next.js with static generation over PostgreSQL, with the collection pipelines split out into separate Python scripts. The two pipelines — the register loader and the site analyser — are deliberately separate: the first runs on a schedule and breaks when an export format changes, the second runs one service at a time and breaks when somebody redesigns their site. Merging them would produce a system that stops entirely for either reason.
What you can take from this
Even if you have no use for the directory, the open data is useful right now.
If you are choosing a contractor in Estonia — ten minutes of checking:
- Find the company in the Business Register by name or registry code.
- Check the year of registration and whether annual reports have been filed for recent years. Gaps are a signal.
- Check the turnover in the exact field of activity you came for, not the total.
- Check the company against the Tax Board's debtor list.
- Confirm that the registry code on the company's website matches the entity you are looking at — site and legal entity are far from always obviously linked.
If you provide services yourself — look at your own company the same way. Is the registry code published on your site, is there a price list with the service composition stated, is it visible how long you have been trading? This is not SEO as such; it is that the data about you is already public and will eventually end up in a table next to your competitors. The only open question is who builds that table first.
If you are building a content project — look for data competitors do not have before you think about structure and copy. Working out what to build your site structure from is far easier once you know what makes your page different from ten identical ones. Without such a source, generating pages programmatically will not rescue it.
What the project deliberately does not do
Limits matter more than capabilities, so here they are directly.
- It does not sell placement. Not through paid profiles, not through advertising. A paid profile buys a logo and a self-written description — and not one position upward.
- It does not rate quality of work. The score on the page describes the completeness and transparency of public data, not whether the company does good work. No reviews are collected.
- It does not broker. The enquiry goes straight to the company; the project is not a party to the transaction.
- It deletes nothing. Rejected records stay in the database with a reason — for re-checking, and for the honest line "there are N companies in this niche, we show M".
- It is Estonian only for now. These services are searched for predominantly in Estonian, and splitting effort before the model is validated is not justified. Why I treat that as a separate decision at all is covered in which language to target for SEO in Estonia.
The next test is not traffic but data: how many services clear the threshold of three providers with verified identifiers. If a niche cannot reach three, it gets no page, however attractive the query looks.
What comes next
The near-term plan is simple: extend the list of services, raise the share of companies with comparable prices, and watch how what is already published gets indexed. I will write about results when there are results — with numbers rather than impressions.
If you need a data-driven content project of this kind — from collection and data model through to web development and search optimisation — this is exactly the work I do on my own projects and on client ones. The only difference is that on my own I risk my own time first.
