# PRH Finland Company Lookup — Official YTJ Business Registry API (`accountable_eel/prh-company-lookup`) Actor

Look up Finnish companies by business ID (Y-tunnus) against the Finnish Patent and Registration Office's official, free YTJ registry. Get company name, legal form, main business line (TOL code), status, and registered address. Pay only for IDs that resolve to a real company.

- **URL**: https://apify.com/accountable\_eel/prh-company-lookup.md
- **Developed by:** [Adrian Voss](https://apify.com/accountable_eel) (community)
- **Categories:** Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 successful lookups

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## PRH Finland Company Lookup

You give it a list of Finnish business IDs (Y-tunnus). It queries Finland's
official company register — maintained by the Patentti- ja rekisterihallitus
(PRH, the Finnish Patent and Registration Office) — and hands back one row per
ID: the company's registered name, legal form, main business line, current
status, and registered address. No scraping, no API key, no login — it calls
[PRH's own open-data YTJ API](https://www.prh.fi) directly, the same source
you'd query by hand on the YTJ website.

Feed it a handful of IDs to check a few counterparties, or a few thousand to
enrich a whole outbound list of Finnish companies. Either way you get back one
dataset row per ID, and you only pay for the ones that actually resolve to a
registered company.

### Who it's for

The accountable\_eel catalogue sells company intelligence columns for outbound. Each actor takes a
list of domains or company identifiers and returns one flat, stably-named row per input —
firmographics, registry IDs, tech stack, email route, hiring activity — the shape a Clay table, an
n8n workflow, or an AI agent can consume without post-processing. Pricing is pay-per-event and
per-domain: a few tenths of a cent for a row that was actually found, and nothing for a
miss, so a list that doesn't enrich costs you next to nothing. Where an official source exists —
VIES, GLEIF, SEC EDGAR, Brønnøysund, PRH, RDAP — it is queried directly instead of scraped. No
seat licence, no monthly minimum, no credit system to decode.

Here, the identifier is a Finnish business ID and the official source is PRH's YTJ registry — the
same register banks, auditors, and Finnish counterparties check before signing anything.

### Why this one

- **Reads the trade register, not a cached status flag.** PRH's raw `company.status` field is a
  constant "2" for every company in the register, active or dissolved — this actor ignores it and
  derives `companyStatus` from the trade register's own register/deregister entries plus bankruptcy,
  restructuring, and liquidation codes, so a status of `Registered (Bankruptcy proceedings)` means
  what it says.
- **One request per business ID, synchronously.** No polling, no async job to check back on — you
  send the batch and read the dataset when the run finishes.
- **Business-ID format is validated before a request is sent.** An input that isn't `NNNNNNN-N`
  comes back `BAD_FORMAT` immediately, without spending a request against PRH.
- **Every entity type PRH registers, not just limited companies.** Public and private limited
  companies, cooperatives, and other registered legal forms are all covered — `companyFormCode`
  and `companyFormDescription` tell you which.
- **A miss still gets you a row.** A business ID that doesn't resolve comes back
  `found: false`, `status: "NOT_FOUND"`, and is never charged.
- **Address comes back as a real object, not one merged string.** Street, postal code, city, and
  country are separate fields inside `address`, so you're not left parsing a free-text address
  block before you can do anything with it.

### What you get

One flat row per business ID:

| Field | Type / format | Description |
|---|---|---|
| `query` | string | The business ID as you submitted it |
| `found` | boolean | `true` if PRH returned a matching company |
| `status` | string | `OK`, `NOT_FOUND`, or `BAD_FORMAT` |
| `businessId` | string, `NNNNNNN-N` | The business ID as registered with PRH |
| `name` | string | Registered company name |
| `registrationDate` | date, `YYYY-MM-DD` | Date the business ID itself was registered |
| `companyFormCode` | string | PRH's numeric code for the legal form (e.g. `16`) |
| `companyFormDescription` | string (Finnish) | The legal form in plain language (e.g. `Julkinen osakeyhtiö`) |
| `mainBusinessLineCode` | string | TOL 2008 business-line code (e.g. `2610`) |
| `mainBusinessLineDescription` | string (Finnish) | The business line in plain language |
| `companyStatus` | string (Finnish) | Derived from the trade register's own entries, e.g. `Registered`, `Ceased`, or `Registered (Bankruptcy proceedings)` |
| `registeredOffice` | string | Registered municipality (kotipaikka), in Finnish |
| `address` | object | `{ street, postCode, city, country }` — registered street address |
| `scrapedAt` | ISO timestamp | When this row was fetched |

`companyFormDescription`, `mainBusinessLineDescription`, and `companyStatus` come back in Finnish —
that's the language PRH's own registry uses. `address` is a nested object, not four flat columns;
if your destination table needs it flat, split it downstream or select just `address` and unpack it
there.

### Price

$4 per 1,000 business IDs, plus a $0.005 start fee. Misses (`found:false`) are never charged.

That's $0.004 per business ID that actually resolves to a company, dropping to $0.0024 on paid
Apify tiers. 1,000 business IDs through this actor: **~$4** if every one is found, less if some
aren't. The same 1,000 rows through a credit-based enrichment platform: **$80–$400**. There's no
seat licence and no monthly minimum — you pay for rows, not for access.

### How to use

1. **In the Apify Console.** Open the actor page and click **Start** — the `businessIds` field is already pre-filled with a working example. Results land in the run's dataset as soon as each item is found.
2. **Via the API.** Call it directly with a POST request — no Console needed once you have an API token:
   ```bash
   curl "https://api.apify.com/v2/acts/accountable_eel~prh-company-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
     -X POST \
     -H "Content-Type: application/json" \
     -d '{"businessIds":["0112038-9","0201256-6"]}'
   ```
3. **On a schedule.** Save this actor as an Apify **Task** with the input you want, then add a **Schedule** (hourly, daily, weekly) so it runs on its own — no server of your own required.

1) Paste your Finnish business IDs into `businessIds`, one per entry, in `NNNNNNN-N` format
   (e.g. `0112038-9`).
2) Turn on `testRun` first if you want to sanity-check the shape of your list against just the
   first 5 IDs before spending on the full batch.
3) Run the actor. Each ID becomes one dataset row — `found: true` rows carry the company record,
   `found: false` rows tell you why (`NOT_FOUND` or `BAD_FORMAT`) and cost nothing.
4) Use `columns` to trim the output to only the fields you need, `onlyFound` to drop misses from
   the dataset entirely, and `includeKeywords` / `excludeKeywords` to filter rows by content (for
   example, keeping only rows where `companyStatus` contains "Registered").
5) Pull the results into your table however you already work with Apify datasets — the API
   directly (see below), the Apify Console's dataset export (CSV, JSON, Excel), or an MCP-connected
   agent that runs the actor and reads the dataset back for you.

There's no separate "search by name" step: PRH's API is keyed on the business ID, so this actor is
built for lists where you already know the ID (from an invoice, a contract, a CRM field, or a prior
enrichment step) and need the registry record that goes with it.

### Input

```json
{
  "businessIds": [
    "0112038-9",
    "0201256-6"
  ]
}
```

One business ID per line, in NNNNNNN-N format. Accepted formats: 0112038-9.

```json
{
  "businessIds": ["0112038-9", "0201256-6"],
  "testRun": false,
  "onlyFound": false,
  "maxConcurrency": 5
}
```

`businessIds` is a list of Finnish business IDs (Y-tunnus), in `NNNNNNN-N` format,
e.g. `0112038-9`. One dataset row is returned per business ID; rows with
`"found": false` are never charged. Turn on `testRun` to try your list against just
the first 5 IDs before running the full batch. Optional `includeKeywords` /
`excludeKeywords` filter rows by content, `maxResults` caps the run early, and
`columns` lets you pick which fields to return.

### Sample output

| query | found | status | businessId | name | registrationDate | companyFormCode | companyFormDescription | mainBusinessLineCode | mainBusinessLineDescription | companyStatus | registeredOffice | address | scrapedAt |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 0112038-9 | true | OK | 0112038-9 | Nokia Oyj | 1978-03-15 | 17 | Public limited company | 70100 | Activities of head offices | Registered |  | {"street":"Karakaari","postCode":"02610","city":"ESPOO","country":null} | 2026-08-23T21:02:34.491Z |

One row per business ID, for example:

```json
{
  "query": "0112038-9",
  "found": true,
  "status": "OK",
  "businessId": "0112038-9",
  "name": "Nokia Oyj",
  "registrationDate": "1902-05-24",
  "companyFormCode": "16",
  "companyFormDescription": "Julkinen osakeyhtiö",
  "mainBusinessLineCode": "2610",
  "mainBusinessLineDescription": "Elektronisten komponenttien valmistus",
  "companyStatus": "REGISTERED",
  "registeredOffice": "Espoo",
  "address": {
    "street": "Karakaari 7",
    "postCode": "02610",
    "city": "Espoo",
    "country": "FI"
  },
  "scrapedAt": "2026-08-21T10:00:00.000Z"
}
```

A business ID that isn't registered with PRH still gets a row (`"found": false`,
`"status": "NOT_FOUND"` or `"BAD_FORMAT"`), so you always get one row per input, but
you're never charged for that.

### Use it from Clay, n8n, Make, or an AI agent

This actor runs synchronously over plain HTTP — call it directly from a script, a workflow tool, or an AI agent, no Apify Console needed once you have an API token.

```bash
curl "https://api.apify.com/v2/acts/accountable_eel~prh-company-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"businessIds":["0112038-9","0201256-6"]}'
```

**n8n.** Add an HTTP Request node: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~prh-company-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body Content Type `JSON`, JSON Body `{"businessIds":["0112038-9","0201256-6"]}` (swap in an expression from an earlier node for a real value).

**Clay.** Add an "HTTP API" column: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~prh-company-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body `{"businessIds":["{{business ID}}"]}`, mapping the row's business ID into the `businessIds` array.

**MCP.** In Claude, Cursor, or any MCP client with the Apify MCP server, ask for "Finland Company Registry Lookup — PRH YTJ API" — the agent will find and run this actor.

### Tips

- Run with `testRun` on first if you're not sure your list is formatted correctly — it costs
  nothing to check 5 IDs against `BAD_FORMAT` before committing the full batch.
- Filter on `companyStatus` rather than assuming every returned row is an active company — a
  dissolved or bankrupt entity is still `found: true`, it's just not a going concern anymore.
- If you only need name and status for a KYB check, set `columns` to just those two fields — you're
  billed the same either way, but a narrower table is easier to work with downstream.
- `mainBusinessLineCode` is a TOL 2008 code, Finland's national adaptation of NACE — useful for
  matching against industry filters that already speak NACE/TOL.
- Business IDs are case- and whitespace-insensitive on the way in, but the format check is strict:
  it has to be seven digits, a hyphen, one check digit.
- Keep the raw `companyStatus` string rather than reducing it to a boolean "active" flag downstream
  — it can carry a bankruptcy, restructuring, or liquidation qualifier alongside the register state,
  and collapsing that to true/false throws away exactly the detail a KYB check needs.
- If you're enriching a list sourced from invoices or contracts, expect some IDs to be typos or
  outdated — `BAD_FORMAT` and `NOT_FOUND` rows are free, so there's no cost to running the whole
  list through and cleaning it based on what comes back.

### vs. alternatives

| | What it costs | What you get | Trade-off |
|---|---|---|---|
| **This actor** (`prh-company-lookup`) | $0.004 per business ID that resolves (less on paid tiers), $0.005 actor start, nothing for a miss | Registered name, legal form, main business line, trade-register-derived status, and address for any Finnish business ID, in a flat row your table can read directly | Finland only, and descriptions come back in Finnish. It doesn't cover filings, ownership, or financials — just the registry record. |
| **PRH's YTJ portal directly** | Free | The same data, from the same source | It's the same source — this actor exists for the batch: hundreds of business IDs in, one row out per ID, retries and format-checking handled, in the format your table already reads. One business ID, once? Use the portal. |
| **Clay** | $0.08–$0.40 per enriched row in credits, on top of a seat | A whole enrichment workspace — waterfalls across dozens of providers, plus the table and the sequencing around it | If you want one place that does everything and you're not counting rows, that's Clay. This is one column, priced per column, callable *from* Clay via its HTTP step. |
| Doing it yourself | Your time, plus keeping up with PRH's field structure (the API's raw `company.status` is a constant across all companies — you'd have to discover and handle that yourself) | The same data | The parsing, the format validation, and the trade-register-vs-status distinction, all absorbed into one call. |

Prices for third-party tools are their published list prices as of August 2026 and are not
tracked here — check the vendor before relying on the comparison.

### FAQ

**What happens if a business ID isn't registered with PRH?**
You get a dataset row with `"found": false` and `"status": "NOT_FOUND"` — PRH's registry has no
company under that ID — and the row is never charged.

**What format does a business ID need to be in?**
`NNNNNNN-N` — seven digits, a hyphen, then one check digit, e.g. `0112038-9`. Anything else is
rejected up front as `BAD_FORMAT`, before a request is even made to PRH, and it's never charged
either.

**Are there rate limits?**
`maxConcurrency` controls how many requests run in parallel against PRH's API (default 5). PRH's
open-data API doesn't publish a documented hard limit, but running very high concurrency against
any public government API risks throttling — the default is a reasonable starting point for large
batches.

**How fresh is the data?**
Each row reflects what PRH's registry returns at the moment the actor calls it — this is a live
lookup, not a cached snapshot. `scrapedAt` on each row is the fetch timestamp.

**Do I need proxies or a PRH account?**
No. PRH's YTJ API is free, public open data — no credentials, no proxy configuration. This actor
calls it directly.

**Is this personal data, and does GDPR apply?**
No — a Finnish business ID and its registered company record are public registry data about a
legal entity, not personal data about an individual. This actor doesn't look up or return
information about company officers, shareholders, or any named individual.

**Can I schedule this to run automatically?**
Yes, through Apify's built-in task scheduler — set up a Store Task against a saved list of business
IDs and run it on a cron schedule from the Apify Console.

**Can an AI agent call this directly?**
Yes — it's registered with the Apify MCP server, so an MCP-connected agent (Claude, Cursor, or
similar) can find and run it by name, no separate integration code needed.

**Does this cover businesses outside Finland?**
No — only entities registered with a Finnish business ID (Y-tunnus) in PRH's YTJ registry. For a
Norwegian counterpart use `brreg-company-lookup`; for a lookup across multiple national registries
in one call, use `company-registry-unified-lookup`.

**Can I look up a company by name instead of business ID?**
No — PRH's own API is keyed on the business ID, not name search, so this actor is too. If you're
starting from a company name, you'll need to resolve it to a business ID first (PRH's YTJ website
supports name search directly).

**Why does `companyStatus` sometimes show two things at once, like "Registered (Bankruptcy
proceedings)"?**
Because a company can be both formally registered and in an insolvency process at the same time —
Finnish company law doesn't deregister a business the moment it enters bankruptcy or restructuring.
This actor reports both facts together rather than collapsing them into one misleading word, and it
reads the trade register's own entries to do it, not PRH's raw status code (which stays "2" for
every company regardless of health).

### Related actors

- [Brreg Norway Company Lookup](https://apify.com/accountable_eel/brreg-company-lookup)
- [Company Registry Unified Lookup](https://apify.com/accountable_eel/company-registry-unified-lookup)
- [EU VAT Validator](https://apify.com/accountable_eel/eu-vat-validator)

# Actor input Schema

## `businessIds` (type: `array`):

One business ID per line, in NNNNNNN-N format. Accepted formats: 0112038-9. You're only charged for the ones we actually find — a miss costs nothing.

## `testRun` (type: `boolean`):

Turn this on to test your input on a small sample before running the full list. Turn it off to process everything.

## `onlyFound` (type: `boolean`):

Only keep rows where something was actually found. Misses are always free, whether or not you show them here.

## `includeKeywords` (type: `array`):

Optional. Only keep results that mention at least one of these words (e.g. a job title, a city, a product name). Leave empty to keep everything.

## `excludeKeywords` (type: `array`):

Optional. Drop any result that mentions one of these words. Leave empty to skip nothing.

## `maxResults` (type: `integer`):

Optional. Stop the run once this many results have been found — useful for a quick, cheap sample. Leave blank for no limit.

## `columns` (type: `array`):

Choose which pieces of information to include in each result row. All are included by default.

## `maxConcurrency` (type: `integer`):

Parallel requests. Keep conservative — this target has no browser fallback, so getting blocked costs more than slow-and-steady.

## `proxyConfiguration` (type: `object`):

Apify Proxy config. Residential recommended for anti-bot-sensitive targets.

## Actor input object example

```json
{
  "businessIds": [
    "0112038-9",
    "0201256-6"
  ],
  "testRun": false,
  "onlyFound": false,
  "includeKeywords": [],
  "excludeKeywords": [],
  "columns": [
    "businessId",
    "name",
    "registrationDate",
    "companyFormCode",
    "companyFormDescription",
    "mainBusinessLineCode",
    "mainBusinessLineDescription",
    "companyStatus",
    "registeredOffice",
    "address"
  ],
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "businessIds": [
        "0112038-9",
        "0201256-6"
    ],
    "includeKeywords": [],
    "excludeKeywords": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("accountable_eel/prh-company-lookup").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "businessIds": [
        "0112038-9",
        "0201256-6",
    ],
    "includeKeywords": [],
    "excludeKeywords": [],
}

# Run the Actor and wait for it to finish
run = client.actor("accountable_eel/prh-company-lookup").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "businessIds": [
    "0112038-9",
    "0201256-6"
  ],
  "includeKeywords": [],
  "excludeKeywords": []
}' |
apify call accountable_eel/prh-company-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,accountable_eel/prh-company-lookup"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/gA511DKd33ZmdM9k8/builds/d5Ij0Nw1YNYaKSgR3/openapi.json
