# Impressum Scraper: Pay per Filled German Imprint (`pradio/imprint-contact`) Actor

Scrape German Impressum pages and pay only for filled imprints, with whichever of company name, address, register court and number, VAT ID, legal representatives, email and phone the page prints. No per-site fee, no start fee beyond the platform's, and a time budget for API and agent runs.

- **URL**: https://apify.com/pradio/imprint-contact.md
- **Developed by:** [Pradio Actors](https://apify.com/pradio) (community)
- **Categories:** Lead generation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.28 / 1,000 imprint record 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: Pay per Filled German Imprint

### What does Impressum Scraper do?

Impressum Scraper reads the Impressum of every company website you give it and returns one row per site, billed per filled imprint. You pay $4.20 per 1,000 filled imprints; a site that yields nothing costs you nothing, and its row says why. A filled row carries up to 37 fields. They are the registered company name and address, register court and number, VAT ID, managing directors, email, phone, fax and social profiles. Paste a list of domains, or type search terms with your own search-API key, and press Start.

#### Your first run: three company sites

```json
{
  "startUrls": [
    { "url": "https://www.trigema.de" },
    { "url": "https://www.stihl.de" },
    { "url": "https://www.mymuesli.com" }
  ]
}
```

### Who uses Impressum Scraper

| Who | What they run it for |
|---|---|
| Contact-data builders | Turn a domain list into legal names, emails and phones from each company's own notice. |
| Compliance and due-diligence teams | Collect register court, register number and representatives without opening each site. |
| B2B sales teams | Check the legal entity and business contact a company publishes before you sign. |
| CRM and data teams | Add registered addresses and registry identifiers to rows that hold a domain. |

### Features

- **Pay per filled imprint.** A row bills at $4.20 per 1,000 when the run reads its notice and pulls a company field off it. Every other outcome returns a row that says why.
- **The imprint page found for you.** It follows the site's own impressum or legal link up to two hops. It tries the contact page when the homepage links to none.
- **Clean values, or none.** Every field is checked against its own shape. A street has a house number and a court is a town, so a menu entry reads as null rather than a board member.
- **JavaScript sites rendered.** A plain request first, then a headless-browser pass for script shells; a block or bot challenge ends the site instead.
- **A refusal ends the site.** A site that refuses, rate-limits or bot-checks you is left alone for the rest of the run, whatever proxy you set.
- **A time budget for API calls and agents.** Each site a deadline cuts off gets a row that says why.
- **Search by term, your key.** Terms resolve to domains through Brave or Google Custom Search. Searches look up businesses; a query that reads as a person's name is refused.
- **Company e-mails checked on request.** A DNS lookup per domain says whether it accepts mail; the check sends nothing.
- **Decision makers with their roles.** Each representative the notice names, with its role when the notice prints one: Geschäftsführer, Vorstand, Inhaber, Managing Director.
- **One column per social network.** Facebook, LinkedIn, XING, Instagram, X and YouTube each in their own column, ready to filter on. Share buttons and a person's own profile stay out.

### Why this one

| | Impressum Scraper | Most-used alternative |
|---|---|---|
| Rows returned on the same site | 1 | 1 |
| Price per 1,000 filled imprints | $4.20 | $5.00 |
| A site that yields nothing | free | $1.20 per 1,000 sites read |
| Charge to start a run | $0.0001 | $0.04 |
| Run memory | 2,048 MB | 1,024 MB |

Both ran on one of the four sites in the example run, on 2026-09-27.

- **Every miss names its kind.** The row names what the site did — not found, refused, timed out — so your list comes back fully answered; a detail fills in when the read produced one.
- **The source is on the row.** Each value links back to the notice page it was read from.

### What data does Impressum Scraper return?

One row per site — two entries on one domain share it. A real row, lists shortened:

```json
{
  "domain": "trigema.de",
  "pageUrl": "https://www.trigema.de/kundenservice/rechtliches/impressum/",
  "company_name": "Trigema W. Grupp KG",
  "street": "Josef-Mayer-Str. 31-35",
  "postal_code": "72393",
  "city": "Burladingen",
  "country": "DE",
  "legal_representatives": "Wolfgang Grupp, Trigema Verwaltung GmbH",
  "decision_makers": [
    { "name": "Wolfgang Grupp", "role": "Persönlich haftende Gesellschafter" }
  ],
  "register_court": "Stuttgart",
  "register_number": "HRA 740365",
  "vat_id": "DE144846566",
  "email": "bestellservice@trigema.de",
  "phone": "+497475880",
  "fax": "+49747588228",
  "linkedin": "https://www.linkedin.com/company/trigema-w-grupp-kg",
  "status": "ok",
  "legal_form": "KG"
}
```

Every field on a row, and what it carries:

| Field | What it is |
|---|---|
| `domain` | The site's hostname without www, from your entry. |
| `start_url` | The start entry this row answers, or the site root a search found. |
| `website` | The site's root URL (https://domain). |
| `websites` | The root plus non-social URLs the page's structured data lists. |
| `pageUrl` | The page actually read; on a miss, the page the run reached. |
| `imprint_url` | The notice page the company fields came from; null when none was found. |
| `title` | The notice page's HTML title on a filled row. |
| `company_name` | The registered company name as the notice prints it. |
| `legal_form` | The entity's legal form, from its name or the page's own words. |
| `legal_representatives` | Every representative the notice names, one line as printed. |
| `decision_makers` | The same representatives as {name, role} pairs, one entry each. |
| `address` | The postal address on one line, joined from checked parts. |
| `street` | The street-and-number line of the postal address. |
| `house_number` | The house number split off the street line. |
| `postal_code` | The postal code in the notice's address block. |
| `city` | The town beside the postal code. |
| `country` | The address's country as a two-letter code, read off the notice. |
| `register_court` | The town of the commercial-register court named. |
| `register_number` | The register number as printed, prefix included (HRA 740365). |
| `register_kind` | Which register the number belongs to, such as `HRA`/`HRB` in Germany. |
| `vat_id` | The VAT identification number stated. |
| `email` | The contact mailbox, own-domain first; department and regulator addresses skipped. |
| `email_mx_ok` | With Check e-mail domains on: whether the domain accepts mail. Null when off or the lookup did not answer. |
| `phone` | The phone number, in international form when the address is German, Austrian or Swiss, else as the page wrote it. |
| `fax` | The fax number, on the same rule as phone. |
| `facebook`, `linkedin`, `xing`, `instagram`, `twitter`, `youtube` | The company's page on that network, linked on the page; a person's own page stays out of the LinkedIn and XING columns. |
| `social_profiles` | Every social profile linked on the page; share buttons stay out. |
| `search_query` | The search term that found this site; null for a listed URL. |
| `status` | The per-site verdict; the values are under Output. |
| `reason` | On a miss, the detail behind the verdict; on a private person's row, what was withheld. |
| `imprint_hash` | A fingerprint of the notice's content that flips when it changes. |
| `row_type` | The row's kind: data row, per-site miss or run-level row. |

A null means the site left that value unpublished; data and miss rows keep the same shape, and a run's summary row adds its three counters.

### How much does it cost?

**$4.20 per 1,000 filled imprints.** One filled imprint is one $0.0042 event on your invoice, charged after the row lands in your dataset. A miss row says why the site yielded nothing and is never billed.

| Filled imprints | You pay | Plus the run's start |
|---|---|---|
| 100 | $0.42 | $0.0001 |
| 1,000 | $4.20 | $0.0001 |
| 10,000 | $42.00 | $0.0001 |

Starting a run costs the platform's per-run event — at this Actor's memory, about $0.0001. A single-site lookup costs well under a cent in all. At the share the test list returned, 100 sites come to about 55 filled imprints and about $0.23. A 1,000-site list comes to about 548 and $2.30.

On paid Apify plans the per-imprint price drops: $3.86 per 1,000 on Bronze, $3.70 on Silver, $3.28 on Gold and above.

Searches run on your own API key, under its terms and quota; the Actor bills no search event of its own.

### How do I use Impressum Scraper?

1. Open the Actor with **Try for free** on its Store page.
2. List your sites in **Start Urls**, one per line, a site root or its imprint page. Or put terms in **Search queries** and they resolve to domains through an official search API.
3. Adjust the run: **Maximum items**, the **Headless browser fallback**, **Check e-mail domains (MX)**, a **Time budget**. **Exclude** takes the domains and names this run leaves out.
4. Press **Start**. Rows land in the dataset as they are read.

#### Worked examples

Check who runs a supplier before you sign:

```json
{
  "startUrls": [{ "url": "https://www.stihl.de" }],
  "maxItems": 1
}
```

Collect business contacts whose mail domains answer, and keep sites with no e-mail out:

```json
{
  "startUrls": [
    { "url": "https://www.trigema.de" },
    { "url": "https://www.mymuesli.com" }
  ],
  "verifyEmails": true,
  "skipWithoutEmail": true
}
```

Find machinery companies through search, on your own Brave key:

```json
{
  "searchQueries": ["site:.de GmbH Impressum Maschinenbau"],
  "searchEngine": "brave",
  "searchApiKey": "<your Brave Search API key>"
}
```

Through the API:

```bash
curl -X POST "https://api.apify.com/v2/acts/Pradio~imprint-contact/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"startUrls":[{"url":"https://www.trigema.de"}]}'
```

### Input

| Input | Type | Default | What it does |
|---|---|---|---|
| `startUrls` | array | 4 example sites | The sites to read, one row per site; two entries on one domain share a row. |
| `searchQueries` | array | none | Terms resolved to domains through an official search API. Search names businesses: a query that reads as a person's name is never sent. |
| `searchEngine` | string | `brave` | `brave` (Brave Search API) or `google` (Google Custom Search JSON API, needs `searchCx`). |
| `searchApiKey` | string | none | Your own key for the engine, kept secret; a query with none is a free `search_failed` row. |
| `searchCx` | string | none | The Google programmable engine ID, for the `google` engine. |
| `proxyConfiguration` | object | Apify datacenter | Site requests go through this proxy group. A refused, rate-limited or bot-challenged site yields a free `fetch_failed` row and is never asked again: no other group, session or browser. |
| `useBrowser` | boolean | on | Render pages that answer as a JavaScript shell. A block or bot challenge is not a shell and is never rendered. |
| `maxItems` | integer | 100 | The most rows one run returns, at most 10,000. |
| `maxRunSeconds` | integer | none | A time budget in seconds, minimum 30; sites it cuts off return as free `aborted` rows. |
| `verifyEmails` | boolean | off | Check each e-mail's shape and its domain's mail servers; a DNS lookup only, nothing sent. |
| `skipWithoutEmail` | boolean | off | A site whose notice names no e-mail returns a free `no_email` row instead of the company row. |
| `maxDomainsPerSearchQuery` | integer | 20 | The most domains one search term may resolve to. |
| `exclude` | array | none | Domains and names to leave out, one per line. A fetched row naming one is dropped, never billed. |

An entry that fails to parse as a URL comes back as a miss row naming it. Two entries on one domain are read once and share a row. For API calls and agents, set the budget a little under the caller's wait — the run stops starting new sites in time to finish inside it.

### Output

Every site on your list gets a row. A filled row's status is ok; every other verdict is a miss row, and its reason says what happened:

| `status` | What it means |
|---|---|
| `ok` | The notice was found and at least one company field was read. The billed verdict. |
| `no_imprint_found` | The site answered but published no findable notice, or it held nothing billable. |
| `fetch_failed` | The site refused, rate-limited or bot-challenged the read, or the connection failed. |
| `js_required` | The page needs a browser and none could read it: the fallback off, or the render gave no text. |
| `timeout` | The host gave no answer. |
| `bad_url` | The entry could not be read as a URL. |
| `search_failed` | The term could not be resolved: a person's name, a missing key or a failed lookup. |
| `aborted` | The time budget or the run's own end cut this site off. |
| `no_email` | With the skip option on, the notice names no e-mail. |

A row's `row_type` marks it: a data row, a per-site miss, or a run-level row with the run's counts. A run summary in your key-value store says what it read, returned and charged, so a short run and a broken one look different.

### Personal data

A row can name a person: a company officer on the company's own legal-notice page. What the product is for, where the data comes from and how removal works:

- **Purpose.** For identifying a company and reaching it through the business contact details it publishes. The rows are for lawful business contact, not for building people databases, profiling or marketing to individuals.
- **Source.** Every row is read from a company's own imprint or legal-notice page, which the company publishes under its disclosure duty. The row links back to that page, and the page's prose stays on the page.
- **Officers in business capacity.** A row names company officers exactly as the notice prints them: name and role. It carries no personal e-mail, handle or phone fields for them.
- **Nothing contacts a person.** The e-mail check is a DNS lookup of the domain's mail records; no mail server is contacted and nothing is sent. A search query that reads as a person's name is refused before it is sent.
- **Objection route.** A company or person that wants its notice left out opens an issue on this Actor's Issues tab. The entry joins an exclusion list every run reads, and a fetched row naming it is dropped, unbilled. Your own Exclude input works the same way.
- **Private persons.** A notice that shows no business at all keeps the person's street, house number, phone and fax back. The row says which fields were withheld.
- **You are the controller.** You need your own lawful basis for the rows your runs collect, for example legitimate interests for B2B contact. You tell the people named where their data came from when you first contact them.
- **Nothing kept.** The Actor keeps no rows between runs; datasets live in your Apify account.

### Limits

- **Coverage is partial.** On the test list of 104 German company sites, 57 came back filled. The misses were 31 fetch failures (14 refused or bot-challenged, 17 whose robots.txt could not be read), 12 sites with no findable notice and 4 timeouts. Of the filled rows: company name 98%, register number 75%, register court 63%, VAT ID 46%, representatives and email 63% each.
- **German- and English-first.** A site that names its notice something outside the usual words comes back as a miss rather than misread.
- **One notice page per site.** The read follows the site's own imprint links, at most two hops and twelve pages. A refusal ends the site there.
- **A field the site leaves unpublished stays null** on your row rather than filled from a guess.
- **Search needs your key.** None is configured for you; a term without one, or a failed lookup, is a miss row naming it.
- **An empty input fails fast.** A run given nothing to read fails at once. A bad entry becomes a miss row and the run carries on.
- **A broken read fails the run.** An error in the reading itself fails the run with the error in its log, rather than handing back rows of nulls.

### FAQ

**What if a site has no legal notice?**
You still get a row for the site that says the notice could not be found. Your list comes back fully answered.

**In which format can I export?**
JSON, CSV, Excel, XML, RSS or an HTML table, from the run's Storage tab or through the API.

**How many domains can it handle?**
As many as you list: a run returns up to 10,000 rows, 100 by default. Sites are read a few at a time with a short pause between hits to one host. A longer list takes longer rather than costing more per site, and a very long list splits across runs.

**Is it legal to scrape imprint pages?**
Imprint pages are disclosures companies publish under a legal duty, and this data is for lawful business contact. Each page is fetched on a path the site's robots.txt allows, with a pause between requests to one host. Data-protection rules still govern what you do with the rows. You are responsible for having the right to read the sites you give it, and for how you use the rows. Personal data says what the product is for and how a site asks to be left out.

**Can I use integrations with Impressum Scraper?**
Yes — Zapier, Make, Google Sheets and webhooks from the Integrations tab, and it runs on a schedule.

**Can I use it through the Apify API or an MCP server?**
Both. The curl line above is the whole API call, wrapped by the JavaScript and Python clients, and the AI-agents section registers it with MCP.

### Not affiliated

This Actor is not affiliated with or endorsed by the sites it reads; it reads what each publishes.

# Actor input Schema

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

The sites to read: each entry is a site root or its imprint/legal-notice page; one output row per site, and two entries on the same domain share one row.

## `searchQueries` (type: `array`):

Optional search terms resolved to domains through an official search API (Brave by default, Google Custom Search with searchCx). Each resolved domain is then read like a startUrls entry. Search looks up businesses, not people: a query that reads as a person's name is not sent and returns a free search_failed row. Bring your own key via searchApiKey, no key is configured for you, so a query sent without one returns a free search_failed row, not a lookup.

## `searchEngine` (type: `string`):

Which official search API resolves searchQueries: brave (Brave Search API, needs searchApiKey) or google (Google Custom Search JSON API, needs searchApiKey AND searchCx).

## `searchApiKey` (type: `string`):

Your own key for the chosen search engine, required for searchQueries. Your key's own terms and quota apply to the searches; this Actor bills nothing per query. The key is kept secret: it never appears in a row, the log or the run summary.

## `searchCx` (type: `string`):

Google Custom Search programmable engine ID (cx), required when searchEngine is google. Create one at https://programmablesearchengine.google.com with 'search the entire web' enabled.

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

Proxy for site requests: every request of the run uses the group you set, datacenter by default. When a site refuses, rate-limits or answers with a bot challenge you get a free fetch_failed row naming it, and it is never asked again: not through another proxy group, a new session or the browser. Pick a residential group for a whole run if you need one: it changes where requests leave from, never whether a site's no is honoured.

## `useBrowser` (type: `boolean`):

When a site answers a plain request with a JavaScript shell, render it headlessly and read the rendered page. A block or a bot challenge is not a shell and is never rendered. Costs more memory and seconds per hit; switch off for cheap list runs over sites that do not need it.

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

The most rows one run returns, at most 10,000; fetching and pushing stop there and the run log notes the cap.

## `maxRunSeconds` (type: `integer`):

Optional. The run stops starting new sites in time to write its rows and finish within about this many seconds, and every site it did not get to comes back as a free aborted row naming it. Set it when an API call or an AI agent waits for the answer, for example 240 under a 300-second wait. Left empty, the run reads the whole list and still stops early enough before its own timeout to write every site it read.

## `verifyEmails` (type: `boolean`):

Check each e-mail address found: is it well formed, and does its domain publish mail servers (MX records)? The answer lands in email_mx_ok. A DNS lookup only; no mail server is contacted and nothing is sent.

## `skipWithoutEmail` (type: `boolean`):

When an imprint names no company e-mail address, return a free no_email row for the site instead of the company record. The row is never billed.

## `maxDomainsPerSearchQuery` (type: `integer`):

The most domains one searchQueries entry may resolve to, at most 20. One broad query cannot starve the rest: each entry gets this many results before the next is asked.

## `exclude` (type: `array`):

One entry per line, a domain (dm.de, or a full URL) or a company or person name. A start URL on an excluded domain is skipped before any fetch; a fetched row whose company_name, legal_representatives or decision_makers names an excluded entry is dropped, never billed. Skips and drops are counted in RUN_SUMMARY under excluded. The Actor's own exclusion list, which a person or company can ask to join, is applied the same way; see the README's Personal data section.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.trigema.de"
    },
    {
      "url": "https://www.stihl.de"
    },
    {
      "url": "https://www.mymuesli.com"
    },
    {
      "url": "https://dm.de"
    }
  ],
  "searchEngine": "brave",
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "useBrowser": true,
  "maxItems": 100,
  "verifyEmails": true,
  "skipWithoutEmail": false,
  "maxDomainsPerSearchQuery": 20
}
```

# Actor output Schema

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

The lookup rows for this run: one per start URL, in the default dataset.

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

Counts for this run: rowsFetched, rowsPushed, rowsCharged, duplicatesDropped and stoppedEarly.

# 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.trigema.de"
        },
        {
            "url": "https://www.stihl.de"
        },
        {
            "url": "https://www.mymuesli.com"
        },
        {
            "url": "https://dm.de"
        }
    ],
    "verifyEmails": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("pradio/imprint-contact").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.trigema.de" },
        { "url": "https://www.stihl.de" },
        { "url": "https://www.mymuesli.com" },
        { "url": "https://dm.de" },
    ],
    "verifyEmails": True,
}

# Run the Actor and wait for it to finish
run = client.actor("pradio/imprint-contact").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.trigema.de"
    },
    {
      "url": "https://www.stihl.de"
    },
    {
      "url": "https://www.mymuesli.com"
    },
    {
      "url": "https://dm.de"
    }
  ],
  "verifyEmails": true
}' |
apify call pradio/imprint-contact --silent --output-dataset

```

## MCP server setup

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

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/YETqPFVDsi2tD4RfK/builds/aLmhXTGdkwBNI9hP7/openapi.json
