# Impressum Scraper: German Company Data & B2B Leads (DE/AT/CH) (`steadyscrape/impressum-scraper`) Actor

Reads the Impressum (imprint) of German, Austrian and Swiss company websites: company, legal form, address, managing directors, register court, HRB/FN/UID, VAT ID, email, phone. From websites, or a trade and city on Google Maps. DACH B2B leads as CSV, JSON or API. Pay per company. No login.

- **URL**: https://apify.com/steadyscrape/impressum-scraper.md
- **Developed by:** [Steady Scrape](https://apify.com/steadyscrape) (community)
- **Categories:** Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 company from an impressums

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 — German, Austrian and Swiss company data from the legal notice

Every commercial website in Germany, Austria and Switzerland must publish an **Impressum** (legal notice): who runs the
site, where, and under which register entry. This Actor reads it and gives you each company as one clean row:
**company name, legal form, address, managing directors, register court and number (HRB / HRA / FN / UID), VAT ID,
email, phone and fax**, plus the company's LinkedIn, XING and other profiles.

Two ways in:

- **A list of websites** you already have (domains or links): the Actor finds the Impressum link and reads it.
- **A trade and a city** (`Steuerberater` in `München`, `Zahnarzt` in `Wien`): the Actor finds the businesses on Google
  Maps and reads the Impressum of every one that has its own website. Each row then also carries the business's Google
  Maps name, category, rating, reviews and link.

You pay only for websites whose Impressum named the company. Unreachable sites, sites without an Impressum, blocked
sites and Maps businesses without a website are free.

### Who it is for

- **B2B sales and lead generation** in the DACH market: decision makers (Geschäftsführer, Vorstand, Inhaber) with the
  company's official address and contacts, by trade and region.
- **CRM enrichment and data cleaning**: add the legal name, register number and VAT ID to a list of domains.
- **KYC, supplier and partner checks**: compare what a company states about itself with the Handelsregister,
  Firmenbuch or Swiss commercial register; the register number and court are given exactly as cited.
- **Agencies and market research**: count and segment local businesses (legal form, city, register) in any area.

### How it works

1. **Websites**: each one is opened like a visitor would. The link to the Impressum is found by its text
   (`Impressum`, `Imprint`, `Legal notice`, `Offenlegung`…); when the homepage shows none (some sites draw their footer
   with JavaScript), `/impressum` and `/imprint` are tried. A link straight to the Impressum is read with one request.
2. **Google Maps**: each search term is looked up around each location, page by page, keeping businesses inside the
   radius. Businesses with their own website are read as above; a profile pointing to Facebook, Instagram or a
   directory counts as no website. A chain whose branches share one Impressum is delivered and charged once.
3. **Reading the Impressum**: the Actor finds the legal-notice block (not the menu, footer or the web designer's
   credits) and reads German, Austrian and Swiss formats: `Amtsgericht Charlottenburg, HRB 269744 B`,
   `FN 440148 b, Handelsgericht Wien`, `CHE-294.954.422 MWST`, `USt-IdNr. DE450479811`, `ATU70099519`, addresses like
   `A – 1010 Wien` or `Graben 31/4/11`, and emails written as `info(at)firma.de` or hidden by Cloudflare's email
   protection.

Measured on 108 small businesses found on Google Maps in Berlin, Vienna, Zurich, Hamburg, Graz and Basel: 87 homepages
answered, 74 Impressums were found, and 69 of those named the company with an address or register entry. The rest
draw their Impressum with JavaScript or show it as an image; they are free rows.

### Input example

```json
{
    "websites": ["https://www.manufactum.de", "einstein-kaffee.de", "mymuesli.com"],
    "searchTerms": ["Steuerberater"],
    "locations": ["München"],
    "radiusKm": 5,
    "maxImprintsPerSearch": 10,
    "country": "de",
    "requireEmail": false
}
```

Use only `websites`, only `searchTerms` + `locations`, or both.

### Output example

```json
{
    "rowType": "imprint",
    "website": "https://hoppesteuerberater.de/",
    "domain": "hoppesteuerberater.de",
    "imprintUrl": "https://hoppesteuerberater.de/impressum/",
    "companyName": "Hoppe & Partner Steuerberatung PartGmbB",
    "legalForm": "PartG mbB",
    "address": "Wagmüllerstraße 16, 80538 München",
    "street": "Wagmüllerstraße 16",
    "postalCode": "80538",
    "city": "München",
    "countryCode": "DE",
    "managingDirectors": ["Danny Hoppe", "Faik Shulemaja"],
    "registerType": "PR",
    "registerNumber": "PR 2549",
    "registerCourt": "Amtsgericht München",
    "vatId": "DE238033672",
    "taxNumber": null,
    "email": "mail@hoppestb.de",
    "emails": ["mail@hoppestb.de"],
    "phone": "089 379 533 76",
    "phoneInternational": "+498937953376",
    "fax": null,
    "socials": { "linkedin": null, "xing": null, "facebook": null, "instagram": null, "youtube": null, "tiktok": null, "x": null },
    "foundBy": "google maps",
    "searchTerm": "Steuerberater",
    "location": "München",
    "mapsName": "Hoppe & Partner Steuerberatung PartGmbB",
    "mapsCategory": "Steuerberater",
    "mapsAddress": "Wagmüllerstraße 16, 80538 München-Altstadt-Lehel",
    "mapsPhone": "089 37953376",
    "mapsRating": 4.8,
    "mapsReviewCount": 26,
    "mapsUrl": "https://www.google.com/maps?cid=16891850753548071026",
    "scrapedAt": "2026-10-21T12:00:00.000Z"
}
```

#### Company fields (`rowType: imprint`, charged)

| field | what it holds |
|---|---|
| `companyName` | The company or, for a sole trader, the person the Impressum names. |
| `legalForm` | Normalized: `GmbH`, `UG (haftungsbeschränkt)`, `GmbH & Co. KG`, `AG`, `KG`, `OHG`, `GbR`, `e.K.`, `PartG mbB`, `e.U.`, `OG`, `Sàrl`, `Einzelunternehmen`… |
| `address`, `street`, `postalCode`, `city`, `countryCode` | The company's address as the Impressum gives it; `countryCode` is `DE`, `AT`, `CH` or `LI`. |
| `managingDirectors` | Geschäftsführer, Vorstand, Inhaber or whoever the Impressum names as representing the company. |
| `registerType`, `registerNumber`, `registerCourt` | `HRB`, `HRA`, `GnR`, `PR`, `VR` (Germany), `FN` (Austria), `UID` (Switzerland), cited as written (`HRB 269744 B`). |
| `vatId`, `taxNumber` | VAT ID (`DE…`, `ATU…`, `CHE-… MWST`) and the German tax number when the Impressum gives one. |
| `email`, `emails` | The company's own mailbox first; web designers' and platforms' addresses are left out. |
| `phone`, `phoneInternational`, `fax` | As written, and the phone in E.164 for dialers and CRMs. |
| `socials` | LinkedIn, XING, Facebook, Instagram, YouTube, TikTok and X profiles linked from the site. |
| `website`, `domain`, `imprintUrl` | Where the data came from. |
| `foundBy`, `searchTerm`, `location`, `maps…` | `website list` or `google maps`; for Maps businesses their name, category, address, phone, rating, reviews and link. |

A field the Impressum does not state is `null` (or an empty list).

#### Free rows

- `rowType: skipped` — a website not delivered, with `reason` and a plain `message`: `unreachable` (no answer, the
  domain does not exist), `blocked` (the site refused the request or answered with a bot check), `noImprint` (no
  Impressum link or page), `noCompanyData` (an Impressum whose HTML names no company: drawn by JavaScript or an image),
  `noEmail` (with "Only companies with an email" on).
- `rowType: search` — one per search term and location: where it was centred, businesses in the radius, how many had no
  website, websites read, companies delivered.

The run's `SUMMARY` record counts everything.

### Pricing

**US$0.003 per company delivered** (`imprint` event), nothing else: no start fee, no per-website fee, no per-page fee.
Everything that is not a delivered company is free. 1,000 companies cost US$3. Set a spending limit on the run and the
Actor stops reading websites when it is reached.

### Google Maps: stops when Google asks for verification

The Actor reads Google Maps' public search answers at a gentle pace. If Google answers with its "unusual traffic"
verification page, the Actor never solves or bypasses it: it stops asking Google for the rest of the run, the searches
left become free `blocked` rows, and the run still ends successfully with what was delivered. Try again later.

### Limits

- Websites that draw their Impressum with JavaScript, show it as an image or put it in a PDF cannot be read (free rows).
- Sites behind bot protection (Cloudflare challenges, DataDome and similar) are free `blocked` rows; the Actor never
  solves a challenge.
- Each Google Maps search returns ~120–240 businesses per point; wide radii are covered with several points.
- Up to 5,000 websites per run, 20 search terms × 20 locations.
- The data is what each company publishes about itself in its Impressum. It can be outdated or incomplete; check the
  official register before relying on it for a contract.

### Is it legal?

The Impressum is a page the law requires companies to publish so that anyone can identify them (Germany § 5 DDG,
Austria § 5 ECG and § 14 UGB, Switzerland Art. 3 UWG). This Actor reads only public pages, at a visitor's pace (one
request at a time per site), never signs in and never bypasses any protection. **Using the data is your
responsibility.** In particular:

- **Cold email and calls**: in Germany, § 7 UWG forbids advertising emails without prior consent, also to businesses,
  and cold calls to businesses need at least their presumed consent; Austria (§ 174 TKG) and Switzerland (Art. 3 UWG)
  have similar rules.
- **GDPR**: names, direct emails and phones of managing directors and sole traders are personal data. You need a legal
  basis (for example legitimate interest, with a balancing test), must inform the people you contact, honour
  objections, and keep the data only as long as needed.

This Actor is **not affiliated with, endorsed or sponsored by Google or by any website whose Impressum it reads**.
Read more in Apify's blog post on [the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/).

### Feedback

Found an Impressum the Actor reads wrong, or a format it misses? Open an issue with the website: every format fixed is
tested against the real page.

# Actor input Schema

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

Company websites, one per line: a domain (<code>manufactum.de</code>) or any link into the site (<code>https://www.firma.at/kontakt</code>). The Actor finds the link to its Impressum and reads it; a link straight to the Impressum is read as it is. Up to 5,000. Websites with no readable Impressum are free rows that say why.

## `searchTerms` (type: `array`):

Find businesses on Google Maps instead, or as well: the trade as you would type it there, one per line (<code>Steuerberater</code>, <code>Zahnarzt</code>, <code>Elektriker</code>, <code>Schreinerei</code>). Every business with its own website gets its Impressum read. Each term is searched in every location. Up to 20.

## `locations` (type: `array`):

Where to search, one per line: a city, district, postcode or address as you would type it into Google Maps (<code>München</code>, <code>Wien 1010</code>, <code>Zürich</code>), or <code>latitude, longitude</code>. Up to 20.

## `radiusKm` (type: `number`):

How far from each location's centre to look, 0.5 to 50 km. Businesses outside it are left out.

## `maxImprintsPerSearch` (type: `integer`):

Stop each search term in each location after this many companies were delivered (1 to 1,000). The run's spending limit is respected too.

## `country` (type: `string`):

The country Google Maps answers for; it decides which town a short name means.

## `requireEmail` (type: `boolean`):

Deliver (and charge) only Impressums that show an email address. The others are free rows with reason <code>noEmail</code>.

## Actor input object example

```json
{
  "websites": [
    "https://www.manufactum.de",
    "einstein-kaffee.de",
    "mymuesli.com"
  ],
  "searchTerms": [
    "Steuerberater"
  ],
  "locations": [
    "München"
  ],
  "radiusKm": 5,
  "maxImprintsPerSearch": 10,
  "country": "de",
  "requireEmail": false
}
```

# Actor output Schema

## `rows` (type: `string`):

No description

## `summary` (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 = {
    "websites": [
        "https://www.manufactum.de",
        "einstein-kaffee.de",
        "mymuesli.com"
    ],
    "searchTerms": [
        "Steuerberater"
    ],
    "locations": [
        "München"
    ],
    "radiusKm": 5,
    "maxImprintsPerSearch": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadyscrape/impressum-scraper").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": [
        "https://www.manufactum.de",
        "einstein-kaffee.de",
        "mymuesli.com",
    ],
    "searchTerms": ["Steuerberater"],
    "locations": ["München"],
    "radiusKm": 5,
    "maxImprintsPerSearch": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("steadyscrape/impressum-scraper").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": [
    "https://www.manufactum.de",
    "einstein-kaffee.de",
    "mymuesli.com"
  ],
  "searchTerms": [
    "Steuerberater"
  ],
  "locations": [
    "München"
  ],
  "radiusKm": 5,
  "maxImprintsPerSearch": 10
}' |
apify call steadyscrape/impressum-scraper --silent --output-dataset

```

## MCP server setup

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

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/QCzt34AraYFsOwtOF/builds/IrmMm342zqxUk3vsd/openapi.json
