Every Romanian company is registered in the ONRC (the national trade register), and the state publishes the whole thing as open data on data.gov.ro. That's 4.2 million companies, 19.3 million CAEN activity records, 3.68 million legal representatives and 4.64 million status entries — a genuinely useful corpus for lead generation, market analysis and "who actually owns this company" lookups.
The catch: it ships as a pile of enormous CSVs, and the moment you try to query it, Romanian orthography punches you in the face. I built a small free API around it, and this is how.
The source
No scraping, no vendor. The official snapshot is a set of CKAN files, the biggest of which is OD_FIRME.csv at ~690 MB and OD_CAEN_AUTORIZAT.csv at ~425 MB. Combined, raw, that's well over a gigabyte of flat files. Loaded into SQLite with a full-text index it becomes a 3.6 GB database that answers a search in milliseconds.
The build
The load is boring and that's the point: stream the CSVs into SQLite, then create an FTS5 virtual table so lookup_business("dedeman") is a real full-text query rather than a LIKE '%dedeman%' scan. Two tables matter most:
-
firme— companies, keyed by CUI (the tax identifier) and registration code -
reprezentanti_legali— the legal representatives, so you can search by person to find every company a director is attached to
On top of that sits a thin FastAPI wrapper exposing two read-only endpoints — no auth, no keys:
# by company name or CUI
curl "https://leadgen.adrianhomelab.com/lookup_business?query=DEDEMAN&max_results=3"
# by director / legal-representative name
curl "https://leadgen.adrianhomelab.com/lookup_director?name=popescu%20ion"
The traps (this is the real content)
Three things will bite you, in order of pain:
1. Diacritics. Romanian uses ă â î ș ț, and the registry stores them faithfully. Popescu and Popéscu are different strings to a naive matcher — but a human searching for "Popescu" expects both. The FTS index has to be diacritic-insensitive: normalize ș→s, ț→t, ă→a on both the indexed text and the query. Miss this and your "obvious" search returns nothing for half the surnames in the country.
2. CUI vs registration code. Every firm has a CUI (digits, sometimes zero-padded) and a registration code like J1992002621040 (court + year + serial). They are not interchangeable, and newcomers constantly pass one where the API expects the other. Supporting both on a single query parameter means testing numeric input against the CUI column first, then falling back to the registration code.
3. CSV scale and encoding. The raw dumps are ;-delimited and large enough that csv.DictReader in a tight loop will keep you up all night. Stream with csv.reader, batch your executemany inserts, and watch your transaction size — a single 690 MB file has to be committed incrementally or SQLite's WAL balloons.
What came out of it
A live, free endpoint:
GET /lookup_business?query=<name|CUI>&max_results=NGET /lookup_director?name=<name>&max_results=N-
GET /docs— interactive OpenAPI docs -
GET /start— free API key: 25 lookups/day for an email address GET /health
The same data is also available as an MCP server (for AI agents) at leadgen-mcp.adrianhomelab.com — Streamable HTTP at the /mcp path. If you're doing anything with Romanian business data — or you just want a worked example of turning a big government CSV dump into a queryable service — the endpoint is live and free to hit. Try it at leadgen.adrianhomelab.com — a free key takes one email.
Run it on Apify without hosting anything: Ro Business Data MCP.
More from me
- Romania's company registry, now callable from Claude and Cursor (MCP + REST)
- Romania's company registry has no free API — so I built one (4.2M firms, MCP + REST)
- Building a SEC EDGAR Filings Scraper: 10-K Risk Factors and 13F Holdings as Clean JSON
- Building a Price & Competitor Monitor That Diffs Shopify and AliExpress Prices
- Building a Vertical Corpus Builder: Clean JSONL Datasets for LLM Fine-Tuning
- Building a Change & Alert Engine That Webhooks Any Diff on Any URL
- Building a Romania Mall Store Directory: Every Store in 29 Malls, One Schema
- Scraping Romanian Public Contracts: A Native-Language Tender & Awarded Deals Scraper
- Building a 12-City US Building Permits Scraper With Python
- Building an EU Safety Gate (RAPEX) Product Recall Scraper With Python
- How I Built a Water Utility Risk Intelligence Tool With Python and MCP
- Building an Aviation Hub API: Airports, Airlines, Live Flights & Weather From Six Keyless Sources
- I Stopped Scraping Business Directories and Built an MCP Server on Official Registry Data
- I Built a Canada Product Recalls & Safety Alerts Scraper That Reads Open Government Data
- I Built a Telegram Members Scraper That Reads Public Chat Stats Without Login
- Building a WHOIS & DNS Lookup Tool: Domain Intelligence in One...
- Building an AI Web Crawler That Outputs LLM-Ready Content Chunks
- Building a Real-Time Press Release Monitor with Python and RSS...
- Building a Universal Property Listing Scraper with Python and ...
- Tracking Tech Sentiment in Real-Time with VADER and Python
- How I Built a Product Hunt Scraper That Tracks Launches in Rea...
- 5 APIs Every Developer Needs for Content Processing (RSS, Extraction, Sitemaps, AI)
- How to Extract Clean Content From Any Website Sitemap (For SEO...
- Scraping 187,000 Romanian Businesses: Building a B2B Lead Gene...
- Make Any Website AI-Readable: Generating llms.txt Files with
- I Built an RSS Aggregator That Extracts Full Article Content (...
Top comments (0)