# Norway Company Data & KYB: Org Number, Roles & Bankruptcy (`datacraft-studio/norway-company-enrichment`) Actor

Look up a Norwegian organisasjonsnummer in the Brønnøysund Enhetsregisteret for KYB and KYC. Legal name, roles, NACE, MVA, employees, bankruptcy (konkurs), liquidation and annual accounts. Norway company data for lead generation.

- **URL**: https://apify.com/datacraft-studio/norway-company-enrichment.md
- **Developed by:** [Yahya Nouali](https://apify.com/datacraft-studio) (community)
- **Categories:** Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 company enricheds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Norway Company Data & KYB

Look up a Norwegian **organisasjonsnummer** and get a KYB dossier from Brønnøysundregistrene: legal name, organization form, NACE, employees, address, MVA (VAT) status, bankruptcy, liquidation, public roles, and annual-account key figures when the open API has them.

The actor is built for lead enrichment and counterparty checks. You pay only when a company is found, when accounts are delivered, or when a monitor run sees a real change. Invalid numbers and misses are free. The first monitor pass stores a baseline and is not charged.

This is not an official Brønnøysund service. Registry data is reused under the [Norwegian Licence for Open Government Data (NLOD)](https://data.norge.no/nlod/en/2.0).

### Use cases

- KYB / KYC onboarding of a Norwegian customer or supplier
- Enrich a lead list of organization numbers with legal name, city, NACE, and employee count
- Check MVA registration before you invoice
- Watch a portfolio for bankruptcy, liquidation, address, name, role, NACE, or VAT changes
- Pull the daily manager, board, and auditor from the public role register

### Input

| Field | Default | Meaning |
| --- | --- | --- |
| `organizationNumbers` | — | 9-digit organisasjonsnummer. Also accepts `923 609 016`, `923.609.016`, and `NO 923609016 MVA`. Mod-11 is checked first. |
| `companyNames` | — | Optional Enhetsregisteret name search. Exact matches win. Partial matches are flagged for review. |
| `includeRoles` | `true` | Public roles. No fødselsnummer. |
| `includeAccounts` | `true` | Regnskapsregisteret key figures when they exist. |
| `includeSubunits` | `false` | Underenheter, capped by `maxSubunits`. |
| `mode` | `enrich` | `enrich` or `monitor`. |
| `monitorKeyValueStoreName` | `norway-kyb-snapshots` | Named key-value store for snapshots. Keep it stable across runs. |
| `maxItems` | `100` | Cap on processed numbers and name hits. |
| `maxNameMatches` | `3` | Name-search hits enriched per query. |
| `minRequestIntervalMs` | `250` | Pause between calls to `data.brreg.no`. |

```json
{
  "organizationNumbers": ["923609016", "NO 923 609 016 MVA"],
  "companyNames": ["Equinor ASA"],
  "includeRoles": true,
  "includeAccounts": true,
  "mode": "enrich"
}
```

More samples live in `examples/`.

### Output

Each company row leads with a risk flag you can filter in the dataset table:

- `CLEAR` — no registry risk signal
- `REVIEW` — annotations, negative equity, accounts more than two years old, or an uncertain name match
- `HIGH` — bankrupt (`konkurs`), under liquidation (`under avvikling`), forced liquidation or dissolution, deleted, or liquidation accounts

The flag is a triage heuristic, not a credit score or a legal opinion. Flat fields (`riskSummary`, `naceCode`, `businessCity`, `accountsRevenue`, `rolesSummary`, `chargedEvents`, …) are there for CSV. Nested `roles`, `accounts`, `subunits`, `sources`, and `changes` are there for JSON. A final `run_summary` row records counts and the prices that applied. Filter `recordType` = `company` when you export.

Unmatched queries are still returned (`result` is `invalid`, `not_found`, or `error`) with an empty `chargedEvents` string.

### Pricing

Pay per event. Configure the JSON in [PUBLISHING.md](PUBLISHING.md) before you sell the actor.

| Event | Price | When |
| --- | ---: | --- |
| `company-enriched` | $0.004 | Enrich mode, and the registry returned an entity or subunit |
| `financials` | $0.002 | Enrich mode, and annual-account figures were delivered |
| `change-detected` | $0.015 | Monitor mode, after the baseline, when a watched field changed |

Not charged: invalid input, not found, request errors, duplicate numbers in the same run, accounts that the API does not have, the monitor baseline, and unchanged monitor checks. Monitor mode does not also charge `company-enriched` or `financials`.

### Monitor mode

Watched fields: registry status, name, bankruptcy, liquidation, forced liquidation, address, roles, NACE, and MVA status.

1. First run writes `NO-{organizationNumber}` into the named key-value store and outputs only the run summary.
2. Later runs output a company row only when one of those fields changed, with `changesSummary` such as `name: EQUINOR ASA -> EQUINOR ENERGY ASA`.
3. Turning roles on after a baseline does not, by itself, count as a change. A failed roles request does not wipe the previous role signature.

Deleting the key-value store starts a new free baseline.

### Sources and licence

Checked against the live services on **24 October 2026**.

- **Enhetsregisteret** — `https://data.brreg.no/enhetsregisteret/api` for entities, subunits, and roles. Official API docs declare **Norsk lisens for offentlige data (NLOD)**. No API key. Contact published by Brønnøysund: `opendata@brreg.no`.
- **Regnskapsregisteret** — `https://data.brreg.no/regnskapsregisteret/regnskap/{organisasjonsnummer}` for key figures from the latest accounts. [data.norge.no](https://data.norge.no/nb/data-services/4dabc5ea-b57b-3b68-9e48-2ff993be3f6e/nokkeltall-fra-arsregnskapet) lists **NLOD 2.0** (`http://data.norge.no/nlod/no/2.0`). The open part covers entities on the ordinary chart of accounts. Banks and insurance companies are generally missing. The service is published as open data with no usage fee; Brønnøysund has described the endpoint as a preview.
- **Kunngjøringer** — there is no open REST API. Announcements are distributed as a paid XML subscription. This actor does not scrape that site. Each row links to the public search page and says the feed is unavailable.

Every company row includes source URLs and the attribution string required by NLOD. Do not present the output as endorsed by Brønnøysundregistrene.

Calls are sequential, at least 250 ms apart by default, with a named User-Agent and retries on HTTP 429 and 5xx (`Retry-After` when it is a number of seconds).

### Integrations: Make, Zapier, n8n

You can call this Actor from no-code tools through Apify's official integrations. Use its Actor ID `JZS4vqRVqHvQdtHhs`, and connect each tool with OAuth or your Apify API token. Never put the token in the Actor input.

- **Make**: in the Apify app, add **Run an Actor** and set *Run synchronously* to Yes. Then add **Get Dataset Items** with the run's default dataset ID, and map the rows to Google Sheets, a CRM or Slack. For long runs, use the **Watch Actor Runs** trigger instead.
- **Zapier**: the Apify app has the **Run Actor** action. A synchronous run has a 30-second hard timeout, so for larger lists start the run and use the **Finished Actor Run** trigger with **Fetch Dataset Items**.
- **n8n**: install the official node `@apify/n8n-nodes-apify`. It is a verified community node on n8n Cloud; on self-hosted n8n, add it under Settings > Community Nodes. Use **Run an Actor and Get Dataset**, or the Apify trigger for finished runs.

Example input:

```json
{"mode": "enrich", "organizationNumbers": ["923609016"], "includeRoles": true}
```

#### Scheduled monitor to Slack or Sheets

1. Save a monitor input as an Actor task and keep the same store name on every run:

```json
{
  "mode": "monitor",
  "organizationNumbers": [
    "923609016"
  ],
  "monitorKeyValueStoreName": "norway-kyb-snapshots"
}
```

2. In Apify Console > **Schedules**, run the task on a cron such as `0 7 * * 1` (every Monday at 07:00), with your timezone selected.
3. On the task's **Integrations** tab, add Slack or an HTTP webhook on *Run succeeded*. You can also use Make **Watch Actor Runs**, Zapier **Finished Actor Run** or the n8n Apify trigger.
4. Fetch the dataset items and keep only the change rows: `recordType` is `"company"` (monitor runs only output changed companies; see `changesSummary`). The first run only stores the baseline and is free.

#### HTTP API

This call runs the Actor and returns the dataset rows in one request. Synchronous runs time out after 300 seconds, so use `/runs` for big lists.

```bash
curl -X POST "https://api.apify.com/v2/acts/JZS4vqRVqHvQdtHhs/run-sync-get-dataset-items" \
  -H "Authorization: Bearer <YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"mode": "enrich", "organizationNumbers": ["923609016"], "includeRoles": true}'
```

### FAQ

**Do I need an API key?**\
No. Both open endpoints used here are keyless.

**What licence applies?**\
NLOD for Enhetsregisteret, and NLOD 2.0 for the open accounts key figures. You must keep attribution. See [PUBLISHING.md](PUBLISHING.md) for the pages that were checked.

**Are roles personal data under GDPR?**\
Role holders published in the open API are public registry data: name, role, and sometimes date of birth. The actor never calls the Maskinporten endpoint and drops `fodselsnummer` if it ever appears. It does not return a national identity number. You are responsible for your own purpose and retention. KYB and AML obligations are a common basis; this README is not legal advice.

**Why was a row free?**\
The number failed mod-11, the registry returned 404 for both the entity and the subunit, the call failed, the monitor run was the baseline, or nothing watched had changed.

**Why are accounts missing for some companies?**\
Sole proprietors, new companies, banks, and insurers often have nothing in the open key-figures API. That is `accountsStatus: not_available` and it is not charged.

**Why are there no kunngjøringer?**\
Brønnøysund does not offer them as open data. Bankruptcy and liquidation flags from Enhetsregisteret are included instead.

**A subunit number was passed. Whose roles are these?**\
Roles and accounts for an underenhet are read from the parent legal entity when the parent number is present. `rolesBelongTo` is `parent`.

### Norsk

**Norske selskapsdata og KYB fra Brønnøysundregistrene.**

Aktøren slår opp organisasjonsnummer i Enhetsregisteret. Du får foretaksnavn, organisasjonsform, næringskode (NACE), ansatte, adresse, MVA-status, konkurs, avvikling, tvangsavvikling, roller og nøkkeltall fra årsregnskapet når de finnes.

Du betaler bare når et selskap blir funnet (`company-enriched`, 0,004 USD), når regnskapstall leveres (`financials`, 0,002 USD), eller når overvåking finner en endring (`change-detected`, 0,015 USD). Ugyldige nummer og treff som ikke finnes, koster ingenting. Første kjøring i monitor-modus lagrer et nullpunkt i et navngitt key-value store og belastes ikke. Senere kjøringer skriver bare ut endringer i status, navn, konkurs, avvikling, adresse, roller, NACE eller MVA.

Godtatte nummerformater er `923609016`, `923 609 016` og `NO 923609016 MVA`. Nummeret kontrolleres med modulus 11 før det sendes noe kall.

Kilder, kontrollert 24. oktober 2026:

- Enhetsregisteret, åpent API uten nøkkel, lisens NLOD. Dokumentasjon: [data.brreg.no](https://data.brreg.no/enhetsregisteret/api/docs/index.html).
- Regnskapsregisteret, åpne nøkkeltall uten nøkkel, lisens NLOD 2.0. Banker og forsikring er som regel ikke med.
- Kunngjøringer har ikke et åpent REST-API. De leveres som XML-abonnement. Aktøren henter dem ikke.

Personvern: Roller fra det åpne API-et er offentlige registeropplysninger (navn, rolle og eventuell fødselsdato). Aktøren kaller ikke det autoriserte API-et og returnerer aldri fødselsnummer. Du må selv ha behandlingsgrunnlag for KYB/KYC. Dette er ikke juridisk rådgivning, og aktøren er ikke en tjeneste fra Brønnøysundregistrene.

Eksempel:

```json
{
  "organizationNumbers": ["923609016"],
  "includeRoles": true,
  "includeAccounts": true,
  "mode": "enrich"
}
```

### Develop locally

Requires Node.js 20 or newer. The published image is `apify/actor-node:20`.

```bash
npm install
npm test
npm run smoke
npm run build
npm run start:dev
```

`npm run smoke` calls the live Brreg APIs for Equinor (`923609016`) and checks that `111111111` is missing. The test suite mocks HTTP and does not need network.

Put an input file at `storage/key_value_stores/default/INPUT.json` before `npm run start:dev`, or copy `examples/input.enrich.json` there. To log pay-per-event locally:

```bash
ACTOR_TEST_PAY_PER_EVENT=true npm run start:dev
```

Package a tarball for `apify push` (no `node_modules`):

```bash
npm run package:actor
```

The archive is `artifacts/norway-company-enrichment-actor.tar.gz`.

### Adding Finland or Denmark

The registry is a `RegistryAdapter` in `src/registry/types.ts`. Norway is `src/registry/norway/`. Register the next country in `src/registry/factory.ts` (`FI` for PRH/YTJ, `DK` for CVR) and map that registry onto `RegistryCompany`. Charging, monitor diffs, risk, and dataset output stay where they are.

### Limitations

- No open kunngjøringer API, so legal announcements are not included.
- Accounts are key figures, not a full PDF filing, and not available for every organization form.
- The open roles API has no fødselsnummer. That is intentional.
- Name search is Brreg free text. Exact normalized names are preferred; other hits are marked `REVIEW`.
- Subunits are capped (`maxSubunits`, default 100).
- Requests are sequential on purpose, so large lists are slow.
- Risk flags are registry heuristics.
- Monitor snapshots belong to the Apify user who runs the actor. A new store name, or a deleted store, baselines again for free.
- The actor does not use a proxy. The upstream APIs are public.

# Actor input Schema

## `organizationNumbers` (type: `array`):

9-digit organisasjonsnummer values. Spaces and dots are ignored. NO 123456789 MVA is accepted. Each number is checked with the mod-11 checksum before any request. Invalid numbers are returned and not charged.

## `companyNames` (type: `array`):

Optional names to search in Enhetsregisteret. An exact name match is used when one exists. Otherwise the top matches are returned and flagged for review.

## `includeRoles` (type: `boolean`):

Board, daily manager, auditor, and other roles from the open roles API. Birth date is included when the registry publishes it. National identity numbers are never requested or returned.

## `includeAccounts` (type: `boolean`):

Key figures from Regnskapsregisteret when the open API has them. In enrich mode this adds the financials event only if figures are returned. Banks and insurers are often absent.

## `includeSubunits` (type: `boolean`):

Underenheter (branches) of a main entity. Capped by Max subunits.

## `mode` (type: `string`):

Enrich returns a dossier per company. Monitor stores a snapshot in the named key-value store and returns only changes. The first monitor run is a free baseline.

## `monitorKeyValueStoreName` (type: `string`):

Named key-value store for monitor snapshots. Use the same name on later runs. Deleting it starts a new free baseline.

## `maxItems` (type: `integer`):

Maximum number of organization numbers and name matches to process.

## `maxNameMatches` (type: `integer`):

How many name-search hits to enrich per company name.

## `maxSubunits` (type: `integer`):

Cap on underenheter saved per company.

## `minRequestIntervalMs` (type: `integer`):

Pause between calls to data.brreg.no. The actor also retries 429 and 5xx responses.

## Actor input object example

```json
{
  "organizationNumbers": [
    "923609016"
  ],
  "companyNames": [],
  "includeRoles": true,
  "includeAccounts": true,
  "includeSubunits": false,
  "mode": "enrich",
  "monitorKeyValueStoreName": "norway-kyb-snapshots",
  "maxItems": 100,
  "maxNameMatches": 3,
  "maxSubunits": 100,
  "minRequestIntervalMs": 250
}
```

# Actor output Schema

## `overview` (type: `string`):

Enriched companies, unmatched queries, and the run summary.

# 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 = {
    "organizationNumbers": [
        "923609016"
    ],
    "companyNames": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("datacraft-studio/norway-company-enrichment").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 = {
    "organizationNumbers": ["923609016"],
    "companyNames": [],
}

# Run the Actor and wait for it to finish
run = client.actor("datacraft-studio/norway-company-enrichment").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 '{
  "organizationNumbers": [
    "923609016"
  ],
  "companyNames": []
}' |
apify call datacraft-studio/norway-company-enrichment --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datacraft-studio/norway-company-enrichment"
        }
    }
}
```

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/JZS4vqRVqHvQdtHhs/builds/3ZCUkdbMjNYaynhEp/openapi.json
