Romania's company registry has no free API — so I built one (4.2M firms, MCP + REST)
I run a side hustle building data tools for AI agents. A few weeks ago I needed
Romanian company data — the kind of thing every lead-gen tool wants: who's
registered, their CAEN activity codes, legal representatives, status, address,
website.
Turns out that data has no free API. Romania's official business registry
(ONRC) publishes everything as open data — a monthly CSV dump on
data.gov.ro — but there's no programmatic way to query it. The only access is
commercial scrapers, or scraping the registry website yourself.
So I built the missing API. Here's the whole thing, from a 1.5 GB government
CSV dump to a live MCP server.
The data is already free — it's just not queryable
Romania publishes its entire registry as open data. Every month, data.gov.ro
drops a snapshot: 4,196,860 companies, 19.3M CAEN activity records, 3.68M
legal representatives, 4.64M status entries. For 2025, it also publishes full
financial statements — balance sheets, revenue, headcount, debt.
The problem is ergonomics, not availability. The files are pipe-delimited CSVs,
a gigabyte and a half each, with no index and no API. You can download them,
but then you have a pile of text.
Turning a CSV dump into a queryable registry
The whole pipeline is three loaders:
-
load_onrc.py— streams the CKAN CSVs straight into SQLite (no pandas loading 1.5 GB into RAM). -
load_nomenclatoare.py— pulls the CAEN activity + status code decode tables, so0125renders as "cultivation of fruit trees" instead of a number. -
load_financiare.py— loads the 2025 financial statements, keyed by CUI (tax code).
Then the important bit: search. Romanian names are full of diacritics —
PAVĂL, ȘTEFAN, ţ. If you search paval, a naive LIKE misses PAVĂL.
So I built an FTS5 full-text index with diacritic-insensitive folding:
ă â î ș ț fold to a a i s t on both the index and the query. Search
popescu and you match Popéscu; search paval and you match PAVĂL.
The result is a 3.5 GB SQLite database that answers "who is this company?" in
milliseconds.
Two surfaces, same data
I exposed it two ways:
-
A REST API (
onrc-api.adrianhomelab.com) — plain JSON over HTTP, with Swagger docs.GET /lookup_business?query=DEDEMAN, done. -
An MCP server (
hermes.adrianhomelab.com/mcp) — Streamable HTTP, so any MCP-capable agent (Claude Desktop, Cursor, etc.) can query it natively with five tools:lookup_business,lookup_director,lookup_financials,extract_contacts,lookup_domain.
Both are free and unauthenticated right now — it's a validation phase, not a
product yet.
The freemium shape
Registry identity (name, CUI, address, CAEN, directors, status) is free. The
financial statements — revenue, profit, employees, assets, debt — are the
paid tier. The scaffolding is already in the repo (monetization.py), held
behind a single MONETIZATION_ENABLED flag. Flip it and paid tools require a
bearer key; free tools get rate-limited.
The point of the flag: prove agents actually want this before I wire billing.
What I learned
- Open data ≠ accessible data. The value isn't the data — it's the diacritic-insensitive search, the join across five tables, and making it one HTTP call away.
- MCP is a distribution channel, not just a protocol. Wrapping the same SQLite DB as an MCP server instantly made it usable by any agent. That's the wedge: the registry is one country, but the pattern generalises.
- Ship the flag before the billing. Monetization scaffolding costs nothing to build and keeps the door open.
The repo is public at
github.com/darksider4all/leadgen-mcp.
The data is Romania's — the queryability is the product.
Companion posts in the same build series:
- Building a Romanian Business Registry API: 4.2M Companies as Queryable JSON — the REST API deep-dive and the diacritics/FTS5 traps
- I Stopped Scraping Business Directories and Built an MCP Server on Official Registry Data — why official open data beats directory scraping
Top comments (0)