# EU Impressum Lookup (`mikee368/eu-impressum-lookup`) Actor

Extract company details from EU websites' Impressum, Imprint, legal notice, Mentions légales or Colofon pages. Returns company data only by default.

- **URL**: https://apify.com/mikee368/eu-impressum-lookup.md
- **Developed by:** [Mike Berckmoes](https://apify.com/mikee368) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 company founds

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

## Impressum Scraper: EU legal-notice company data from a list of domains

**Turn a list of company websites into clean company records taken from each site's legally required legal notice.** The legal notice is called Impressum in Germany, Austria and Switzerland, Mentions légales in France, Colofon in the Netherlands, Note legali in Italy, and Imprint, Legal notice or company information in the UK and Ireland. For each domain you get the legal name, legal form, register court and number, VAT ID, address, generic company email and phone. Use it for B2B lead enrichment, CRM cleaning, KYB/supplier checks and matching websites to legal entities. DACH comes first. French, Dutch, Italian, Belgian, Luxembourg, UK and Irish pages also work.

### What does Impressum Scraper do?

For every domain it:

1. reads `robots.txt` and respects it (including `Crawl-delay`, max 5 s),
2. fetches the homepage over plain HTTP (no browser, no proxy),
3. finds the legal-notice link (Impressum, Imprint, Legal notice, Mentions légales, Colofon, Note legali, Notizie legali, company information, Offenlegung, Rechtliche Hinweise …) and opens the best candidates,
4. if no link is visible, tries common paths such as `/impressum`, `/imprint` and `/mentions-legales`, then looks for an Impressum URL in the site's `sitemap.xml` (listed in robots.txt),
5. parses the page and returns **one row per domain** with a `status`.

| Field | Examples / formats recognised |
|---|---|
| `legalName`, `legalForm` | GmbH, AG, SE, KG, GmbH & Co. KG(aA), UG (haftungsbeschränkt), e.K., GesmbH, Genossenschaft, SAS/SASU/SA/SARL/SCA (also spelled out: "société par actions simplifiée"), B.V., N.V., S.r.l., S.p.A., S.r.l.s., BVBA, SPRL, Ltd … |
| `registerType`, `registerNumber` | **HRB/HRA/GnR/PR/VR** (DE), **FN** (AT Firmenbuch), **CHE-UID** (CH, check digit verified), **SIREN** (FR, Luhn verified, also read from RCS/SIRET), **KvK** (NL, 8 digits), **CRN** (UK company number), **CRO** (Ireland, 6 digits), **REA** and **P.IVA** (Italy, P.IVA Luhn-checked), **KBO/BCE** (Belgium, check digit), **RCS** B-number (Luxembourg) |
| `registerCourt` | Amtsgericht / Registergericht, Handelsgericht / Landesgericht, Handelsregister des Kantons …, RCS + city |
| `vatId`, `vatIdChecksumValid` | USt-IdNr., UID (ATU…), TVA (FR..), BTW (NL…B..), BE, IT, LU, GB, IE, CHE … MWST; check digit verified for DE, AT, FR, NL, BE, IT, LU and CHE. GB and IE numbers are kept with a null check result |
| `street`, `postcode`, `city`, `country` | DE/FR/IT 5-digit, AT/CH/BE/LU 4-digit (also `A-`/`CH-`/`B-`/`L-` prefixes), NL `1234 AB`, UK postcode (`SW1A 1AA`), Irish Eircode (`A65 F4E2`) |
| `email`, `phone` | Generic company addresses only (info@, kontakt@, contact@, office@, service@ …); phone from Tel/Telefon/Phone/Tél labels |
| `status` | `ok`, `no_impressum`, `fetch_error`, `robots_disallowed` |
| `confidence` | 0–1 completeness score (see the dataset schema) |

### Why use it?

- **Cheap and light.** Plain HTTP requests only: no browser, no proxies, no search-engine fees.
- **One row per domain, always.** Failures are labelled (`fetch_error` with the HTTP status or error, `no_impressum`), so you can join the output back to your list.
- **Validated identifiers.** VAT IDs, Swiss UIDs and French SIRENs are check-digit verified, so typos and look-alike numbers are flagged or dropped.
- **Polite by design.** robots.txt is obeyed (an unreachable robots.txt counts as "disallow", per RFC 9309). Requests to the same site are sequential, at least 0.5 s apart. The crawler identifies itself as `EUImpressumLookup`.
- **Company data only by default** (see GDPR below).

### How to use it

1. Paste domains or URLs into **Domains or URLs** (e.g. `example.de`, `https://www.example.at/`), **or** pass an Apify **dataset ID** from a Google Maps / places scraper (each item's `website` / `websiteUrl` / `url` is mapped to a domain), **or** paste `mapsItems` inline.
2. Optionally turn on **Verify VAT via VIES** (default off) to check extracted USt-IdNr numbers with the free EU REST API.
3. Optionally change **Max parallel domains** (default 10) and the **Request timeout**.
4. Run, then export to CSV, Excel or JSON, or connect Google Sheets, Make, Zapier or your CRM.

### Input example

```json
{
  "domains": ["example.de", "https://www.example.at/", "example.fr"],
  "maxConcurrency": 10,
  "includePersonNames": false,
  "verifyVat": false
}
```

Google Maps dataset:

```json
{
  "domains": [],
  "datasetId": "YOUR_PLACES_DATASET_ID",
  "maxDatasetItems": 200,
  "verifyVat": false
}
```

### Output example

```json
{
  "input": "example.de",
  "domain": "example.de",
  "status": "ok",
  "foundVia": "link",
  "sourceUrl": "https://www.example.de/impressum",
  "legalName": "Example GmbH",
  "legalForm": "GmbH",
  "registerType": "HRB",
  "registerNumber": "HRB 12345",
  "registerCourt": "Amtsgericht München",
  "vatId": "DE123456789",
  "vatIdChecksumValid": true,
  "street": "Musterstraße 1",
  "postcode": "80331",
  "city": "München",
  "country": "DE",
  "email": "info@example.de",
  "phone": "+49 89 123456-0",
  "confidence": 1.0,
  "requests": 3,
  "checkedAt": "2026-10-03T18:00:00+00:00"
}
```

`vatIdChecksumValid` is a check-digit test, not a VIES lookup. Set `verifyVat` to call `POST https://ec.europa.eu/taxation_customs/vies/rest-api/check-vat-number` (free, no key). `vatVerified` is true only when VIES returns `valid: true`.

### How much does it cost?

**$2 per 1,000 domains with company data found** ($0.002 per `ok` row, event `company-found`), pay per event, with no start fee. Every input domain still gets one row, but rows with `no_impressum`, `fetch_error` or `robots_disallowed` are free. 500 domains with 300 `ok` rows cost $0.60. If your maximum charge per run is reached, the remaining domains are not checked (`notCheckedDueToChargeLimit` in `RUN_SUMMARY`).

Optional event **`vat-verified`**: charged only when VIES confirms a number (`valid: true`). Proposed $0.001–0.002. This event is **not published** and Store prices are not changed by this upgrade; until PPE is configured the charge call is ignored on a private Actor.

### GDPR and person names

Impressum pages often list managing directors (Geschäftsführer), board members or a publisher by name, and sometimes a personal email address. **These are personal data under the GDPR. By default this Actor does not output them:** only generic company emails (info@, kontakt@ …) are returned, and no names.

If you switch on **Include person names**, the Actor adds `managingPersons` and may return a non-generic email address. Only do this if you have a lawful basis, for example legitimate interest for B2B outreach with an opt-out. Inform the data subjects where Art. 14 GDPR requires it, respect objections, and store and delete the data according to your own policies. You are the controller for the data you collect with this Actor.

### Limitations (honest)

- **Bot-protected sites** (Akamai, Cloudflare and similar) answer plain HTTP clients with HTTP 403 or time out. They come back as `fetch_error`, because this Actor does not use proxies or browsers. In our test this affected many large retail and consumer brands.
- **JavaScript-only sites** whose homepage and legal page are rendered in the browser give `no_impressum`.
- **Geo-redirects.** Some global `.com` sites redirect to a country version (e.g. the US site) that has no Impressum. Pass the country URL instead (e.g. `https://www.example.com/de-de/`).
- **Groups with several entities.** The Actor returns the main operator of the site. When a page lists several registrations (e.g. a dual-seat AG), the first one is returned.
- **Register court and wording are returned as printed.** For example, it may return "Vienna Commercial Court" or "Berlin-Charlottenburg".
- Text is parsed with rules, not AI. Unusual layouts can produce missing fields. Check `confidence`.

### Test results (local, 3 Oct 2026)

22 well-known DE/AT/CH/FR company sites: **12 ok, 4 no_impressum, 6 fetch_error** (HTTP 403 or timeouts from bot protection). For all 12 ok rows, the legal name and register number match the legal-notice page. 7 of these were also matched to the GLEIF LEI register: the legal name matched in 7/7, the register number in 6/7 (the 7th is a dual-seat company where the page and GLEIF list different but valid registrations), and the postcode in 7/7. Every VAT ID that the EU VIES service could check (6/6) was valid. VIES did not answer for German numbers during the test.

### FAQ

**Is scraping an Impressum legal?** The Impressum is information that companies must publish (e.g. § 5 DDG in Germany, § 25 MedienG in Austria, LCEN in France). This Actor reads only that page plus the homepage, respects robots.txt and returns company data by default. You are responsible for how you use the results.
**Why is a domain `fetch_error` when it opens in my browser?** The site blocks non-browser clients, or its robots.txt is unreachable. Retry later or check it manually.
**Can it find the company website from a company name?** No. The input is domains, a Google Maps dataset id, or `mapsItems` with a website field.
**Does verifyVat change the $0.002 company-found price?** No. `company-found` stays $0.002. `vat-verified` is a separate optional event, pricing left unset.

# Actor input Schema

## `domains` (type: `array`):

Company websites to check, e.g. 'example.de' or 'https://www.example.at/'. One row per entry. The homepage is fetched, the Impressum / Imprint / Legal notice / Note legali / Mentions légales / Colofon / company-information link is followed and the legal-notice page is parsed.

## `includePersonNames` (type: `boolean`):

Off by default. When on, adds 'managingPersons' (names listed as Geschäftsführer, Vorstand, Vertreten durch, Directeur de la publication, …) and allows a non-generic contact email. These are personal data under the GDPR: only enable it if you have a lawful basis (e.g. legitimate interest for B2B) and handle the data accordingly.

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

How many domains are processed in parallel. Requests to one domain are always sequential with at least 0.5 s between them (or the robots.txt crawl-delay, max 5 s).

## `requestTimeoutSecs` (type: `integer`):

Timeout per HTTP request.

## `maxLegalPagesPerDomain` (type: `integer`):

How many candidate legal-notice links to open per domain (best-scored first). The Actor stops early when a page gives a complete record.

## `tryCommonPaths` (type: `boolean`):

If the homepage has no recognisable legal-notice link (e.g. it is built with JavaScript), try /impressum, /imprint, /de/impressum, /en/imprint, /mentions-legales, /legal-notice, /colofon, /note-legali, /legal and /company-information.

## `useSitemap` (type: `boolean`):

If no legal-notice page was found via links or common paths, look for an Impressum / Imprint / Mentions légales URL in the site's sitemap.xml (up to 4 sitemap files per domain).

## `datasetId` (type: `string`):

Optional Apify dataset id from a Google Maps or places scraper (or any dataset with a website URL). Each item's website / websiteUrl / url / domain is mapped to a host and run through the same Impressum lookup. Combined with Domains. Clear the domain list if you only want dataset URLs.

## `maxDatasetItems` (type: `integer`):

How many rows to read from datasetId.

## `mapsItems` (type: `array`):

Optional list of Google Maps / places objects. The same website fields as dataset items are used. Use this instead of a dataset id when you already have the items.

## `verifyVat` (type: `boolean`):

When on, each extracted USt-IdNr / VAT ID is checked with the free EU VIES REST API (POST check-vat-number, no key). Adds vatVerified / vatVerifiedName / vatVerifiedAddress. A successful valid=true check emits the optional pay-per-event vat-verified (proposed $0.001–0.002; Store pricing is not changed by this Actor). Invalid or unavailable checks are not charged. Default off.

## Actor input object example

```json
{
  "domains": [
    "siemens.com"
  ],
  "includePersonNames": false,
  "maxConcurrency": 10,
  "requestTimeoutSecs": 15,
  "maxLegalPagesPerDomain": 3,
  "tryCommonPaths": true,
  "useSitemap": true,
  "maxDatasetItems": 500,
  "verifyVat": false
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset with one row per domain

## `summary` (type: `string`):

Counts per status, duration and effective input

# 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 = {
    "domains": [
        "siemens.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("mikee368/eu-impressum-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 = { "domains": ["siemens.com"] }

# Run the Actor and wait for it to finish
run = client.actor("mikee368/eu-impressum-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 '{
  "domains": [
    "siemens.com"
  ]
}' |
apify call mikee368/eu-impressum-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mikee368/eu-impressum-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/bdLsHYKz0IoUztaA9/builds/D7pxS8bKKG2Qyl1mh/openapi.json
