DEV Community

Oaida Adrian
Oaida Adrian

Posted on Originally published at dev.to

Romania's company registry has no free API — so I built one (4.2M firms, MCP + REST)

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:

  1. load_onrc.py — streams the CKAN CSVs straight into SQLite (no pandas loading 1.5 GB into RAM).
  2. load_nomenclatoare.py — pulls the CAEN activity + status code decode tables, so 0125 renders as "cultivation of fruit trees" instead of a number.
  3. 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:

Top comments (0)