Quick answer
Finland's PRH/YTJ open-data API is free, keyless, and genuinely open — but its JSON nests every company 3-4 levels deep and quietly accepts filter parameters it never applies. The Finland PRH Company Registry Scraper pages the /v3/companies endpoint, flattens each entry into one English-labelled row (business ID, name, form, industry line, address, dissolution status), and prices at $4.05 per 1,000 companies.
The two things that break a naive PRH client 🧊
Names and addresses are lists, not fields — and the first entry isn't necessarily the current one. PRH stores every historical company name as an entry in a names array, each with its own endDate. A client that reads names[0] gets whichever name PRH happened to write first, which for a renamed company can be the old name. The same shape shows up for company forms and business lines. This Actor walks the array for the entry with no endDate and only falls back to the first entry when nothing is marked current.
A filter parameter can return 200 OK and full data while doing absolutely nothing. PRH's API silently ignores companyForm, pageSize, and size — send them and you get a normal-looking response with every company in it, not an error telling you the param was rejected. A dashboard built against the documented shape can ship a "filter by company form" feature that quietly filters nothing, for months, before anyone diffs the row count against an unfiltered call. This Actor sends only the query params confirmed against the live API and leaves the rest out rather than shipping an input that lies.
Underneath both of those, the run still has to survive an ordinary day on a government API: pages that 429 or 500 under load, an entry with a malformed or missing businessId block buried on page 40, and a search narrow enough to legitimately return zero rows. We retry transient 429/5xx responses with capped exponential backoff and rotating Chrome/Firefox/Safari TLS fingerprints, skip and log any one malformed entry without killing the run, and finish a genuine zero-match search as a clean SUCCEEDED status — not a failure you get billed for.
The judgement call: we removed a filter the API "accepts" 🔍
PRH's schema documents a companyForm query parameter. It's tempting to expose it in the input form — one more filter looks like one more feature. We didn't, because it's a no-op: pass any value and PRH returns the identical unfiltered result set with a 200. A customer who sets it would get back a dataset that looks filtered and isn't, and there's no error to tip them off — just a row count that's quietly wrong.
Devil Scrapes ships the filters that are confirmed to work — name, location, business ID, postal code, industry code, and registration-date range — and leaves the rest out. Fewer inputs, none of them lying, beats a filter list that matches the docs but not reality.
What you get per row
Business ID (Y-tunnus) and the date it was registered, current company name, company form code and its English label, main business line code and English description, registration date, dissolution date and an is_dissolved flag, street address (with a PO-box fallback when there's no street), postal code, city, website when registered, a has_special_situation flag (bankruptcy, restructuring, and similar PRH-flagged states), the last-modified timestamp, and a direct YTJ lookup URL for the business ID.
Company forms and business lines come back in English where PRH publishes an English description; names, streets, and cities fall back to Finnish where no English text exists — PRH just doesn't translate everything.
What it costs
Pay-Per-Event: $0.05 per run plus $0.004 per company row. A thousand companies runs ≈$4.05 — no subscription, no minimum, and a narrow search that matches nothing costs only the start fee, never a per-row charge for rows that don't exist. Apify hands every new account $5 of free credit, enough to run this end to end before spending anything.













