# Impressum Scraper - Company, VAT ID & Register Number Extractor (`neverempty/impressum-company-data-extractor`) Actor

For DACH B2B lead lists and KYB checks: an Impressum API that finds the legal notice (imprint, mentions légales, aviso legal, 特定商取引法) of each website and returns name, legal form, address, phone, email, directors, register number and court, VAT ID with a format check, each with its source line.

- **URL**: https://apify.com/neverempty/impressum-company-data-extractor.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (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 $5.00 / 1,000 website with company data returneds

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?

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 - Company, VAT ID & Register Number Extractor

Give a list of websites. For each one this Actor finds the **Impressum** (legal notice, imprint, *mentions légales*, *aviso legal*, *note legali*, 特定商取引法に基づく表記) and returns one row with the company behind the site: legal name and legal form, address, phone, email, managing directors, commercial register number and court, and VAT ID. **Every value comes with the line of the page it was read from**, so a person or an AI agent can cite and check it.

Use it for B2B lead lists in the German-speaking market, KYB / supplier checks, invoice address checks, or to answer "which company runs this website?".

### What makes it different

- **Evidence for every field.** `evidence.companyName`, `evidence.address`, `evidence.register`, `evidence.vatId` … hold the exact line(s) the value was read from. Nothing is inferred by a language model; the reading is done with rules, and a field the rules are not sure about is left `null`.
- **It does not confuse the operator with other companies on the page.** A German Impressum also names the web designer (*Webdesign*), the hosting company (*Hosting*), the supervisory authority (*Aufsichtsbehörde*), the dispute resolution body (*Streitschlichtung*) and the person responsible for content (*Verantwortlich i.S.d. § 18 MStV*). Those sections are recognised and not used for the company name, address, phone, register or VAT ID. The content-responsible person is returned separately in `contentResponsible`.
- **VAT ID format check.** `vatIdCheck` says whether the VAT ID (*USt-IdNr.*, *UID*, *TVA*, *P.IVA*) has the right length for its country and, for DE, AT, BE, FR, IT, GB and CH, whether its check digit is right (`format-and-check-digit`, `format-only`, `check-digit-failed`, `format-invalid`). This is not a VIES lookup.
- **Several languages and countries (German-speaking sites are the checked ones; the rest is best effort, see Limits).** German (DE / AT / CH: HRB, HRA, FN, CHE-UID, Amtsgericht, Firmenbuchgericht), French (RCS, SIREN/SIRET), Spanish (NIF/CIF, Registro Mercantil), Italian (P.IVA, REA), English (Companies House number) and Japanese (特定商取引法に基づく表記, 会社概要, 法人番号).
- **Honest misses, free of charge.** A site without a findable legal notice, a site that refuses the reader, or a page whose text cannot be read comes back as a row with a `status` and a `note` that says why. Those rows are not charged.
- **Polite by design.** Only the sites you list are read, at most 5 pages per site. Each site's `robots.txt` is read at run time and pages disallowed for `User-agent: *` are not requested (`robots-disallowed`). Check pages (CAPTCHA) and HTTP 403 are not solved and not routed around with proxies (`blocked`).

### Input

| Field | What it does |
| --- | --- |
| `websites` | Domains (`example.de`) or legal notice URLs (`https://example.de/impressum`), 1 to 500. A domain is searched for its legal notice; a URL with a path is read as the legal notice. |
| `includePersonNames` | Default `true`. Turn off to leave out `representatives`, `contentResponsible` and any company name without a legal form (it may be a sole trader's personal name). |
| `maxConcurrency` | Websites read in parallel (1 to 8, default 4). |
| `requestTimeoutSecs` | Time limit per request (5 to 60, default 20). |

```json
{ "websites": ["www.hetzner.com", "www.thomann.de", "www.zotter.at"], "includePersonNames": true }
```

### Output

One row per website. Example (shortened):

```json
{
  "status": "ok",
  "domain": "zotter.at",
  "impressumUrl": "https://www.zotter.at/impressum",
  "impressumFoundVia": "homepage-link",
  "companyName": "Zotter Schokolade GmbH",
  "legalForm": "GmbH",
  "street": "Rastbühel 7d",
  "postalCode": "8301",
  "city": "Laßnitzhöhe",
  "countryCode": "AT",
  "phone": "+43 - 3152 - 5554",
  "phoneE164": "+4331525554",
  "email": "schokolade@zotter.at",
  "representatives": [{ "name": "Julia Zotter", "role": "Geschäftsführerin" }],
  "registerType": "FN",
  "registerNumber": "FN 220619s",
  "registerCourt": "Graz",
  "vatId": "ATU53816900",
  "vatIdCountry": "AT",
  "vatIdFormatValid": true,
  "vatIdCheck": "format-and-check-digit",
  "fieldsFound": 7,
  "evidence": {
    "companyName": "Zotter Schokolade GmbH",
    "address": "Rastbühel 7d / 8301 Laßnitzhöhe",
    "phone": "Tel.: +43 - 3152 - 5554",
    "representatives": "Geschäftsführerin: Julia Zotter",
    "register": "Firmenbuchnummer: 220619s / Firmenbuchgericht: Landesgericht für ZRS Graz",
    "vatId": "UID: ATU53816900",
    "country": "vat-id-prefix: ATU53816900"
  }
}
```

| Column | Meaning |
| --- | --- |
| `status` | `ok`, or the reason nothing was returned: `no-impressum-found`, `impressum-not-readable`, `blocked`, `robots-disallowed`, `robots-unreachable`, `unreachable`, `not-found`, `login-required`, `http-error`, `unreadable`, `not-html`, `bad-input`, `budget-reached`. |
| `impressumUrl`, `impressumFoundVia` | The page that was read and how it was found: `homepage-link`, `legal-page-link`, `homepage` (one-page sites), `common-path`, `input-url`. |
| `companyName`, `legalForm` | The operator as written, and its legal form (GmbH, UG (haftungsbeschränkt), AG, GmbH & Co. KG, e.K., e.V., Ltd, PLC, SAS, SARL, S.L., S.r.l., 株式会社 …). `legalForm` is `null` when the name carries none. |
| `street`, `postalCode`, `city`, `region`, `countryCode`, `country`, `addressRaw` | The first address that belongs to the operator. `countryCode` is filled only when the page itself gives it (country written in the address, a `D-`/`A-`/`CH-` prefix, the VAT ID prefix, a Japanese or UK postcode); it is never taken from the domain ending. |
| `phone`, `phoneE164` | The phone number as written, and in international form when the number starts with `+`/`00` or the country was read from the page. Fax numbers are skipped. |
| `email` | The first email address in the operator's part of the notice (also `name [at] example.de` and Cloudflare-protected addresses, as a browser shows them). |
| `representatives` | `[{ name, role }]`: Geschäftsführer, Vorstand, Inhaber, Managing Director, Gérant, Directeur de la publication, 代表者, 運営統括責任者 … |
| `contentResponsible` | The person named as responsible for content (V.i.S.d.P., § 18 MStV, § 55 RStV). |
| `registerType`, `registerNumber`, `registerCourt` | For example `HRB` / `HRB 6089` / `Ansbach`, `FN` / `FN 220619s` / `Graz`, `RCS`, `SIREN`, `Companies House`, `NIF/CIF`, `REA`, `UID`, `法人番号`. |
| `vatId`, `vatIdCountry`, `vatIdFormatValid`, `vatIdCheck` | The VAT ID without spaces, and the result of the format check. |
| `fieldsFound`, `fieldsRead` | How many of the seven groups were read (company name, address, phone, email, representatives, register, VAT ID) and which. |
| `evidence` | For each group, the line(s) the value was read from. Values taken from the page's JSON-LD (`Organization`) say so, for example `JSON-LD Organization.legalName: …`. When the JSON-LD name carries no legal form and the page text states the same name with one ("Monzo Bank Limited"), the text is used. |
| `dataSources` | Per group: `impressum-page-text` or `json-ld`. |
| `note` | What was not read, and the limits of the row. |

### Pricing

Pay per event: **`impressum-returned`** is charged once for each website whose legal notice was found and from which at least two of the seven groups were read. Rows with any other `status` are free. There is no start fee. An empty input reads the example site and charges nothing.

### Limits you should know

- **Where it has been checked.** The reading rules were checked in production (2026-10-05) on German, Austrian and Swiss websites: of 30 mixed sites, 16 came back with company data, 10 refused automated readers, disallowed the page in robots.txt or could not be reached, 1 needed JavaScript and 3 had no findable notice. French (*mentions légales*), Spanish (*aviso legal*), Italian (*note legali*) and Japanese (特定商取引法に基づく表記) notices are read by rules that are in place but **have not been confirmed in production yet: treat those as best effort** and check `evidence`.
- **JavaScript-only pages are not read.** If the legal notice text appears only after JavaScript runs (for example dm.de), the row is `impressum-not-readable` and free.
- Reading is done with rules, not a language model. Legal notices written as one long sentence, as an image, in a column layout where labels and values are far apart, or loaded by JavaScript after the page opens can yield fewer fields or `impressum-not-readable`. Missing fields are `null`; nothing is guessed.
- For a group of companies the notice may name several entities. The first operator block from the top of the notice is taken. For a GmbH & Co. KG the notice also names the general partner; check `evidence.companyName`.
- Sites that answer HTTP 403 or show a check page to automated readers come back as `blocked`. Shopify shops disallow `/policies/` in robots.txt, so a legal notice that lives only there comes back as `robots-disallowed`.
- The United Kingdom has no Impressum duty; company details there are often only in the page footer or the terms, so fewer UK sites are found.
- The VAT ID check is a format and check-digit check. It does not say the number is registered or belongs to this company.
- Legal notices contain personal data that the law requires traders to publish (names of directors and owners; for sole traders sometimes a home address or mobile number). You are responsible for how you use it: follow the GDPR and your country's rules on unsolicited marketing (in Germany § 7 UWG), and use `includePersonNames: false` if you do not need names.

### FAQ

**What is an Impressum?** In Germany (§ 5 DDG, formerly § 5 TMG), Austria (§ 5 ECG, § 25 MedienG) and Switzerland (Art. 3 UWG) commercial websites must state who operates them. France (*mentions légales*), Spain (*aviso legal*, LSSI) and Italy (*note legali*) have similar duties, and Japanese online shops must publish a 特定商取引法に基づく表記.

**Can an AI agent use this?** Yes. Give it the domain; the row answers "who operates this site" and `evidence` gives the sentence to quote, with `impressumUrl` as the source.

**Does it verify the VAT ID in VIES?** No. It checks length and check digit only.

### Thanks for using this Actor

We build these tools for people who run them every day, and we improve them from what users tell us.

- **Missing a field, or need another filter?** Tell us in the **Issues** tab. If the data is there, we add it.
- **Found a bug or a wrong value?** Post the run ID in the **Issues** tab. Wrong data is the thing we fix first.

If this Actor saved you time, a short review helps other people find it.

# Actor input Schema

## `websites` (type: `array`):

Websites whose legal notice you want read, one per line (1 to 500 per run). Give a domain (example.de) and the Actor finds the Impressum / imprint / legal notice / mentions légales / aviso legal / note legali / 特定商取引法に基づく表記 page from the home page links or the usual addresses (/impressum, /imprint, /legal-notice, /legal …). Give a full URL with a path (https://example.de/impressum) and that page is read as the legal notice. Checked in production on German, Austrian and Swiss sites; French, Spanish, Italian and Japanese notices are read by rules that have not been confirmed in production yet (best effort). Pages whose text appears only after JavaScript runs cannot be read. Only the sites you list are read, at most 5 pages per site, and each site's robots.txt is followed. The same site given twice is read and charged once. If this is empty, the example site www.hetzner.com is read and nothing is charged.

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

Legal notices name managing directors, owners and the person responsible for content; for sole traders the business name is often a personal name. Turn this off to leave out representatives, contentResponsible, and any company name that carries no legal form (GmbH, Ltd, SAS …). You are responsible for using personal data lawfully (GDPR, rules on unsolicited marketing).

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

How many websites are read in parallel (1 to 8).

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

How long to wait for one page to answer (5 to 60 seconds). HTTP 429, 500, 502, 503 and 504 are asked again up to two more times.

## Actor input object example

```json
{
  "websites": [
    "www.hetzner.com",
    "www.thomann.de",
    "www.zotter.at"
  ],
  "includePersonNames": true,
  "maxConcurrency": 4,
  "requestTimeoutSecs": 20
}
```

# Actor output Schema

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

One row per website: the legal notice URL and how it was found, company name and legal form, address, phone, email, representatives, commercial register number and court, VAT ID with format check, and for every value the line it was read from (evidence). A website whose legal notice could not be found or read comes back as a free row that says why.

# 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 = {
    "websites": [
        "www.hetzner.com",
        "www.thomann.de",
        "www.zotter.at"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/impressum-company-data-extractor").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 = { "websites": [
        "www.hetzner.com",
        "www.thomann.de",
        "www.zotter.at",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/impressum-company-data-extractor").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 '{
  "websites": [
    "www.hetzner.com",
    "www.thomann.de",
    "www.zotter.at"
  ]
}' |
apify call neverempty/impressum-company-data-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/impressum-company-data-extractor"
        }
    }
}
```

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/rSOuntfYHkx64UZjY/builds/Da8zmUKFCfvtry4Af/openapi.json
