# German Imprint (Impressum) Scraper (`confidential_gnat/german-imprint-scraper`) Actor

Finds the Impressum page on any German website and extracts the company's decision makers, legal name, address, email addresses, phone numbers, commercial register number and VAT ID as structured data using AI.

- **URL**: https://apify.com/confidential\_gnat/german-imprint-scraper.md
- **Developed by:** [ActorFlow](https://apify.com/confidential_gnat) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

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

## German Imprint (Impressum) Scraper with AI Extraction

**Scrape the Impressum from any German website** and get the company behind it as structured data. Give this **German imprint scraper** a list of domains and it finds each site's Impressum page automatically, then uses **AI to extract the decision makers** — managing directors, board members, owners — along with the legal company name, address, **email addresses**, phone numbers, commercial register number and VAT ID. Export to JSON, CSV or Excel, or call it as an **API**.

**Target website:** any German website — the actor locates the Impressum page itself

### ✨ Features of this German Impressum scraper

- **Automatic imprint discovery** — finds the Impressum page from just a domain, no need to know its URL
- **AI decision maker extraction** — names *and* roles for Geschäftsführer, Vorstand, Inhaber and the person responsible for content, always on
- **Contact details attached to the right person** — an email or phone the page states for a named decision maker is returned on that person, not lost in a flat list
- **Full company details** — legal name, legal form, street, postal code, city, country
- **General company contact data** — support/press/service emails and phone numbers that aren't one person's own
- **Registration data** — Handelsregister court and number, plus the USt-IdNr (VAT ID)
- **Handles bot-blocked and JavaScript-rendered sites** — a site that returns a 403, or whose footer only appears after rendering, gets a second pass in a real browser automatically
- **Works from a bare domain** — paste `example.de`, a home page, or the Impressum URL itself
- **Repeat-run caching** — name a cache project and later runs skip sites already processed
- **Proxy support** — optional, and switched off by default

### 🚀 How to scrape a German Impressum in 5 steps

1. [Sign up](https://apify.com/sign-up) for a free Apify account — includes **$5 monthly credit**.
2. Open the actor page and click **Try for free**.
3. Paste the websites you want into **Websites** — domains or full URLs both work.
4. Click **Start** and wait for the run to complete.
5. Download results from the **Output** tab in JSON, CSV, or Excel format.

You can also run this actor via the [Apify API](https://docs.apify.com/api/v2) or integrate it directly into your workflows using [Zapier](https://zapier.com/apps/apify), [Make](https://www.make.com/), or [n8n](https://n8n.io/).

### 💰 Pricing

- New Apify accounts include **$5 of free monthly credit**.
- Most sites are handled with plain HTTP requests, which are fast and cheap. A browser only opens for a site that returned a 403 or carried no links at all, so the majority of runs never pay browser cost.
- Proxies are disabled by default, which keeps runs at their cheapest.
- **The Actor's default input (`otto.de`, `thomann.de`) returns a saved snapshot instead of a live AI run** — see "Default input returns a saved snapshot" below. Any other input is always scraped live.

### 📌 Default input returns a saved snapshot

When `startUrls` is left as the Actor's own default — exactly `otto.de` and `thomann.de`, no more, no fewer — the run returns a **saved snapshot from a prior real scrape** instead of extracting live with AI, and the run log says so explicitly. This exists because platforms that run Actors automatically on their default input as a daily health check would otherwise trigger a live, AI-billed extraction every day for input nobody actually chose, and AI usage on this Actor is billed to its developer rather than the caller.

**This only affects the exact default site pair.** Provide any other websites — a single site, a superset that includes `otto.de`/`thomann.de` alongside others, or entirely different domains — and the run always scrapes live, exactly as described everywhere else in this README. If you specifically want fresh data for `otto.de` or `thomann.de`, add at least one other site to the input, or remove one of the two, and that run will scrape live.

### 🔧 Input configuration

| Field                | Type    | Required | Default                    | Description                                                                                                                                                                                                                                                |
| -------------------- | ------- | -------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `startUrls`          | array   | —        | `otto.de`, `thomann.de`    | Websites to process. A bare domain, a home page, or an Impressum URL all work — one website always produces exactly one result. **Note:** leaving this at the exact default pair returns a saved snapshot rather than a live scrape — see below.           |
| `useBrowserFallback` | boolean | —        | `true`                     | Retry a site in a real browser when it returned a 403 over plain HTTP, or when its raw HTML carried no links at all. A site still blocked after that gets one more attempt on a residential proxy — scoped only to sites still stuck, never the whole run. |
| `cacheProjectName`   | string  | —        | —                          | Name a project to remember processed sites across runs.                                                                                                                                                                                                    |
| `proxyConfiguration` | object  | —        | `{"useApifyProxy": false}` | Proxy settings. Off by default.

**Supported input types:**

- Bare domain — `otto.de`
- Home page — `https://www.otto.de`
- Impressum URL — `https://www.otto.de/service/impressum/`

### 📦 German imprint scraper output data

Each result is a JSON object with the keys `site`, `imprintUrls`, `discoveryMethod`, `companyName`, `legalForm`, `decisionMakers`, `street`, `postalCode`, `city`, `country`, `companyEmails`, `companyPhones`, `registerCourt`, `registerNumber` and `vatId`. `decisionMakers` is an array of `{name, role, email, phone}` objects — `email`/`phone` are filled only when the page states that contact detail for that specific person, otherwise `null`. `companyEmails`/`companyPhones` hold addresses and numbers that belong to the company in general (support, press, service desks) rather than one named person. `imprintUrls` lists every page merged into the row — usually one, occasionally more when discovery found the imprint split across pages. `discoveryMethod` records how the first page was found, so you can audit any row. The dataset ships with three views: **Overview**, a compact table of site, company, city and company emails; **Decision makers**, focused on the named people and their own contact details; and **Full company details**, which adds the address and registration fields.

**Sample output:**

```json
[
    {
        "site": "https://www.flaconi.de",
        "imprintUrls": ["https://www.flaconi.de/impressum/"],
        "discoveryMethod": "choice",
        "companyName": "Flaconi GmbH",
        "legalForm": "GmbH",
        "decisionMakers": [
            { "name": "Bastian Siebers", "role": "Geschäftsführer (Vorsitzender)", "email": null, "phone": null },
            { "name": "Alexandra Szarmach", "role": "Geschäftsführer", "email": null, "phone": null },
            { "name": "Henry Brodski", "role": "Geschäftsführer", "email": null, "phone": null }
        ],
        "street": "Franklinstraße 15a",
        "postalCode": "10587",
        "city": "Berlin",
        "country": "Deutschland",
        "companyEmails": [],
        "companyPhones": ["030 / 920 363 63"],
        "registerCourt": "Amtsgericht: Berlin-Charlottenburg",
        "registerNumber": "HRB 133604",
        "vatId": "DE 815 275 589"
    },
    {
        "site": "https://www.dm.de",
        "imprintUrls": ["https://www.dm.de/impressum-43984"],
        "discoveryMethod": "browser-choice",
        "companyName": "dm-drogerie markt GmbH + Co. KG",
        "legalForm": "GmbH + Co. KG",
        "decisionMakers": [
            { "name": "Christoph Werner", "role": "Geschäftsführer (Vorsitzender)", "email": null, "phone": null },
            {
                "name": "Jan-Henrik Mende",
                "role": "Öffentlichkeitsarbeit und Pressefragen",
                "email": "presse@dm.de",
                "phone": null
            }
        ],
        "street": "Am dm-Platz 1",
        "postalCode": "76227",
        "city": "Karlsruhe",
        "country": "Deutschland",
        "companyEmails": ["servicecenter@dm.de", "presse@dm.de", "authorities@dm.de", "report@dm.de"],
        "companyPhones": ["0721 / 55 92 0", "0800 / 3 65 86 33"],
        "registerCourt": "Amtsgericht Mannheim",
        "registerNumber": "HRB 104238",
        "vatId": "DE143619945"
    }
]
```

### 🐍 How to scrape a German Impressum with Python, JavaScript or the API

Run the actor programmatically with the official Apify clients. Replace `<YOUR_API_TOKEN>` with the token from your [Apify Console](https://console.apify.com/account/integrations).

**Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")

run = client.actor("<username>/german-imprint-scraper").call(run_input={
    "startUrls": [{"url": "https://www.otto.de"}],
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["companyName"], item["decisionMakers"])
```

**JavaScript** (`npm install apify-client`):

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

const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });

const run = await client.actor('<username>/german-imprint-scraper').call({
    startUrls: [{ url: 'https://www.otto.de' }],
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

**cURL** — start a run and wait for the dataset:

```bash
curl -X POST "https://api.apify.com/v2/acts/<username>~german-imprint-scraper/run-sync-get-dataset-items?token=<YOUR_API_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"startUrls": [{"url": "https://www.otto.de"}]}'
```

### 💡 What you can use German Impressum data for

- **B2B lead generation** — reach the named decision makers at companies in your market
- **Sales prospecting** — enrich a domain list with company names and direct contacts
- **Compliance and KYC checks** — verify a trading partner's register number and VAT ID
- **Company data enrichment** — attach legal name, address and registration data to a CRM
- **Market research** — map who runs the companies in a sector
- **Due diligence** — confirm who legally represents a business before contracting

Sales teams, B2B marketers, compliance officers, researchers and recruiters use this data across e-commerce, finance, professional services and market intelligence.

### ⚠️ Limitations & known issues

- **Sites with strong bot protection** — a small number of sites block both plain requests and headless browsers. They are reported in the run log as unresolved rather than returning a partial row; enabling residential proxies usually helps.
- **Obfuscated email addresses** — some sites assemble their addresses in JavaScript. Those placeholders are filtered out rather than stored as fake addresses, so such a row may have an empty `companyEmails` array.
- **Not every imprint names a person** — sole traders and some GmbHs list only the company. `decisionMakers` is then an empty array, which is a correct result, not a failure.
- **A person's own contact detail is only attached when the page is explicit about it** — most imprints list emails and phone numbers at the company level rather than per person, in which case they land in `companyEmails`/`companyPhones` and each decision maker's `email`/`phone` stays `null`.
- **German-language pages** — the extraction prompt targets German imprint conventions (§5 TMG). It works on English "Legal notice" pages of German companies, but is not built for other jurisdictions.

### ❓ Frequently asked questions

#### Can I scrape a German Impressum legally?

The Impressum is a page German law (§5 TMG) *requires* websites to publish openly, and this actor only reads that public page — no login or private content is accessed. Scraping publicly available data is generally considered lawful (see *hiQ Labs v. LinkedIn* as precedent). Note that imprint pages name real people, so the data is personal data under the GDPR: you need a lawful basis to store or use it, and you must honour the purpose limitation and the data subject's rights. Using it for unsolicited bulk email may breach the GDPR and German competition law (UWG) — check your obligations before you send.

#### How does the scraper find the Impressum page?

It reads every same-domain link on the site and picks out the one most likely to be the imprint, rather than only matching a fixed set of keywords — so it still finds the right page when a site labels its legal link unconventionally. If that pick (or nothing) turns out not to be a real imprint, it falls back to trying the conventional paths (`/impressum`, `/de/impressum`, `/legal-notice`, and a dozen others). A candidate page only counts once it also carries real imprint wording, so an empty page returning HTTP 200 is never mistaken for the imprint. A site that returns a 403 over plain HTTP, or whose raw HTML carries no links at all (a JavaScript-rendered shell), gets a second pass in a real browser, and — only if it is still blocked after that — one final attempt on a residential proxy.

#### What is a decision maker in this context?

Any person the imprint names as representing the company: Geschäftsführer (managing director), Vorstand (board member), Inhaber (owner), Vertretungsberechtigte (authorised representative), and the person named responsible for content under § 18 Abs. 2 MStV. Each comes back with their role where the page states it.

#### Do I need a proxy to scrape German websites?

Usually not, which is why proxies are off by default. If a site still blocks the plain-HTTP and browser passes, the actor automatically retries just that site on a residential proxy — you don't need to enable one yourself for occasional blocks. Set `proxyConfiguration` to residential proxies from the start if you are processing a large list of sites known to block datacenter traffic.

#### How do I scrape an Impressum with Python?

Install `apify-client`, then call the actor with your domain list and iterate the dataset — see the Python example above. Each company comes back as flat JSON ready for a CRM import.

#### Can I run this Impressum scraper on a schedule?

Yes. Use [Apify Schedules](https://docs.apify.com/platform/schedules) to re-run it, and set `cacheProjectName` so each run skips sites already processed and only handles newly added ones.

#### What output formats are supported?

JSON, CSV, Excel, XML and RSS, either from the **Output** tab or through the Apify API.

### 🇩🇪 Kurzanleitung (Deutsch)

Dieser Actor findet automatisch das **Impressum** einer deutschen Website und extrahiert die Firmendaten daraus.

**So funktioniert's:**

1. Trage unter **Websites** eine oder mehrere Domains ein (z. B. `otto.de`) — eine Startseite oder die Impressum-URL selbst funktionieren ebenso.
2. Klicke auf **Start** und warte, bis der Lauf abgeschlossen ist.
3. Lade die Ergebnisse im **Output**-Tab als JSON, CSV oder Excel herunter.

**Erfasste Daten:** Firmenname, Rechtsform, Anschrift, Vertretungsberechtigte (Geschäftsführer, Vorstand, Inhaber) mit Rolle und — sofern angegeben — persönlicher E-Mail/Telefonnummer, allgemeine Firmen-E-Mails und -Telefonnummern, Registergericht, Handelsregisternummer und USt-IdNr.

**Hinweis:** Dieser Actor liest ausschließlich die öffentlich zugängliche Impressumsseite — kein Login, keine privaten Inhalte. Da Impressen echte Personen namentlich nennen, handelt es sich um personenbezogene Daten im Sinne der DSGVO; für Speicherung und Weiterverwendung ist eine Rechtsgrundlage erforderlich.

### 🔗 Other actors you may find useful

- [Google News AI Scraper](https://apify.com/confidential_gnat/google-news-ai-scraper) — search Google News by keyword, optionally extract full article text and AI-generated summaries, with built-in cross-run deduplication.
- [Google Patents Scraper](https://apify.com/confidential_gnat/google-patents-scraper) — scrapes patent listings and details from Google Patents.
- [UniAcco Student Accommodation Scraper](https://apify.com/confidential_gnat/uniacco-property-scraper) — scrapes student accommodation listings from UniAcco, including price range, address, amenities, rating and reviews.
- [HousingAnywhere Property Scraper](https://apify.com/confidential_gnat/housinganywhere-property-scraper) — scrapes rental listings from HousingAnywhere, including price, city, coordinates, room count, floor area, house rules, facilities and images.
- [Woolworths Products Scraper](https://apify.com/confidential_gnat/woolworths-products-scraper) — scrapes Woolworths Australia grocery products: name, brand, price, unit price, package size, availability, ingredients, allergens and nutrition.

### 💬 Support & Contact

If you encounter any issues or have questions, please [open an issue](https://apify.com/confidential_gnat/german-imprint-scraper/issues/open)

You can also find more of my actors on the page [Actor Flow ](https://apify.com/confidential_gnat).

# Actor input Schema

## `startUrls` (type: `array`):

Websites to find the Impressum for. Paste a domain (example.de), a home page, or the Impressum URL itself — the actor finds the imprint page automatically in each case and always uses AI to extract company details, decision makers and their contact info.

## `useBrowserFallback` (type: `boolean`):

When a site returns a 403 over plain HTTP (a bot wall), retry it once in a real browser. Sites that respond normally never open a browser, so this costs nothing on the majority of sites.

## `cacheProjectName` (type: `string`):

Optional. Name a project to remember which websites have already been scraped across runs, so repeated runs skip them.

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

Proxy settings. Most sites are reachable without a proxy, so proxies are disabled by default. Enable residential proxies if you are scraping sites that block datacenter traffic.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.otto.de"
    },
    {
      "url": "https://www.thomann.de"
    }
  ],
  "useBrowserFallback": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `overview` (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 = {
    "startUrls": [
        {
            "url": "https://www.otto.de"
        },
        {
            "url": "https://www.thomann.de"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("confidential_gnat/german-imprint-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 = { "startUrls": [
        { "url": "https://www.otto.de" },
        { "url": "https://www.thomann.de" },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("confidential_gnat/german-imprint-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 '{
  "startUrls": [
    {
      "url": "https://www.otto.de"
    },
    {
      "url": "https://www.thomann.de"
    }
  ]
}' |
apify call confidential_gnat/german-imprint-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,confidential_gnat/german-imprint-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/7ru1ba32N69K9PIMf/builds/M7kv6pjAVe4BpJCSk/openapi.json
