# German Impressum Contact Scraper (`pradio/imprint-contact`) Actor

German Impressum Contact Scraper reads a list of company websites, or you can provide a search-API key to find them, and returns each site's Impressum/legal notice: company name, address, register court and number, VAT ID, directors, email, phone. Proxy and headless-browser fallback.

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

## Pricing

from $3.87 / 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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## German Impressum Contact Scraper

German Impressum Contact Scraper reads the Impressum of every company website you give it and returns one row per site: the registered company name, address, register court and number, VAT ID, managing directors, email and phone. Paste a list of domains or type search terms with your own search-API key, press Start, and the rows land in your dataset as they are read. A site that yields nothing costs nothing, and its row says why.

On 100 German company sites it had never seen, run where a buyer runs it, 60% came back filled, at $0.00496 a row and no start fee of its own.

### Who uses German Impressum Contact Scraper

| Buyer | What they run it for |
|---|---|
| Contact-data builders | Turn a list of domains into legal entity names, emails and phone numbers pulled from each company's own legal page. |
| Compliance and due-diligence teams | Collect register court, register number and managing directors, the disclosures an imprint is required to carry, without opening each site by hand. |
| B2B sales and outreach teams | Find the real legal entity and a working contact behind a website before writing to it. |
| Data teams enriching company lists | Add registered address and registration identifiers to rows that only hold a domain. |

### Features

- **Automatic imprint discovery.** The site's own impressum, imprint or legal-notice link is followed up to two hops: homepage to legal hub to notice. The contact page is tried when the homepage carries no link. A related host is followed across a locale redirect. The conventional paths (/impressum, /imprint…) are probed when the footer only exists after hydration.
- **Dual fetching.** A plain request first, then a headless browser pass when the page needs one (`useBrowser`, on by default).
- **Proxy escalation a site has to earn.** Datacenter requests first. A page that answers with a block is retried once through residential, never before. Configure RESIDENTIAL groups yourself to proxy everything.
- **Search mode, bring-your-own-key.** Type terms instead of pasting URLs. They resolve to domains through the Brave or Google Custom Search API with your key, then read like any start URL.
- **Legal-notice vocabulary, German- and English-first.** Impressum, imprint, legal notice/information, Anbieterkennzeichnung, mentions légales, colophon, disclaimer.
- **Exclusion that is honoured, not promised.** The `exclude` input and the Actor's own exclusion list are read at the start of every run. A domain skips its start URLs before any fetch. A company or person name drops the row that carries it. See Personal data.

### What you can count on

- You pay only for rows whose `status` is `ok`. A site that refused the request, hid its imprint behind JavaScript, or publishes none is pushed as an uncharged `ITEM_STATUS` row. The dataset still tells you what happened.
- Every billed row is charged only after it is written to your dataset. A row you cannot see is never billed.
- A run that finds nothing returns one PROFILE\_NOT\_FOUND row that says so, never an empty dataset.
- A spending limit stops the run cleanly. A STOPPED\_EARLY row says how many rows were returned and how many were not.
- Every run writes RUN\_SUMMARY to the key-value store: rowsFetched, rowsPushed, rowsCharged, duplicatesDropped, excluded. A short run and a broken one are told apart.
- No value is invented. A field the page does not show is null, and the field table below says which.

### What data does German Impressum Contact Scraper return?

One row per start URL. This is a real row from a captured run, slightly shortened:

```json
{
  "pageId": "trigema.de",
  "domain": "trigema.de",
  "website": "https://trigema.de",
  "pageUrl": "https://www.trigema.de/kundenservice/rechtliches/impressum/",
  "title": "Impressum | trigema",
  "email": "bestellservice@trigema.de",
  "phone": "+497475880",
  "websites": [
    "https://trigema.de"
  ],
  "imprint_url": "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",
  "managing_director": "Wolfgang Grupp, Trigema Verwaltung GmbH",
  "register_court": "Stuttgart",
  "register_number": "HRA 740365",
  "vat_id": "DE144846566",
  "social_profiles": [
    "https://facebook.com/trigema",
    "https://www.instagram.com/trigema_de/",
    "https://www.tiktok.com/@trigema_de",
    "https://www.linkedin.com/company/trigema-w-grupp-kg",
    "https://www.youtube.com/@TRIGEMAtv",
    "https://instagram.com/trigemafashion",
    "http://www.facebook.com/TRIGEMA",
    "https://www.youtube.com/user/TRIGEMAtv",
    "https://twitter.com/TRIGEMA"
  ],
  "status": "ok",
  "legal_form": "KG",
  "result": "ROW"
}
```

Every field, and where it is read from:

| Field | What it is |
|---|---|
| `pageId` | The site's bare domain, taken from the start URL's hostname. Also the key rows are de-duplicated on. |
| `pageUrl` | The page actually read for this site: the imprint/legal-notice URL when one was found, else the start page that was fetched. |
| `title` | The `<title>` text of the page that was read. |
| `email` | The page's own-domain contact mailbox. Department mailboxes (privacy, press, careers, accessibility) and regulator addresses are never returned: they answer a function, not a contact. A page that lists only those yields null. |
| `phone` | The page's `tel:` link normalised to E.164 (`+497475880`), else a `Tel:`/`Phone:` line in the text. |
| `website` | The site's root URL (`https://<domain>`), derived from the start URL's hostname. Never null. |
| `websites` | The site root plus every non-social URL the page's JSON-LD Organization lists under `sameAs`. Social links never land here; they go to `social_profiles`. |
| `domain` | The site's hostname without `www.`, taken from the start URL. |
| `imprint_url` | The URL of the imprint/legal-notice page the site links to, followed from the fetched page and read for the company fields. Null when none was found. |
| `company_name` | The registered company name: the first company-shaped line in the imprint body (GmbH, AG, SE, Ltd. …), else the page's JSON-LD Organization name. Never invented from the domain; null when nothing qualifies. |
| `street` | The street-and-number line of the postal address stated in the imprint body. |
| `postal_code` | The postal code in the address block of the imprint body. Only counted near the street or register lines, so a price like "1398" elsewhere on the page is not mistaken for one. |
| `city` | The city named beside the postal code in the imprint body's address. |
| `country` | The country the imprint body names, as a two-letter code (DE, AT, CH …), or taken from the VAT ID prefix. Never guessed from the domain, so it is null on pages that do not name one. |
| `managing_director` | The managing director(s) or other legally responsible person(s) named on the imprint's `Geschäftsführer:` / `Vertreten durch:` / `Vorstand` / `Managing Director:` line. Multi-line lists are kept whole, so one name is never mistaken for the list. |
| `register_court` | The commercial-register court the imprint names (`Registergericht` / `Amtsgericht` / "registered at"). |
| `register_number` | The commercial-register number the imprint carries (`HRB` / `HRA` …). |
| `vat_id` | The VAT identification number the imprint states (`USt-IdNr` / `VAT ID`), whitespace stripped. For example `DE144846566`. |
| `social_profiles` | Every social-media profile linked anywhere on the page: anchors to known social hosts plus the JSON-LD Organization's `sameAs` links. Null when none are linked. |
| `status` | This source's per-URL verdict: `ok`, `no_imprint_found`, `fetch_failed`, `bad_url`, `timeout`, `js_required`, `search_failed` or `aborted`, meaning why a row has no company data. Any verdict but `ok` is free (see `result`). |
| `legal_form` | The entity's registered legal form parsed from the company name: `AG`, `GmbH`, `SE`, `KGaA`, `SE & Co. KGaA`, `e.V.`… Null when the name declares none. |
| `result` | `ROW` on a filled row (billed); `ITEM_STATUS` on a per-site miss (pushed so you see the verdict, never billed); `PROFILE_NOT_FOUND` and `STOPPED_EARLY` on the run-level status rows described under Output. |

Fields that appear only on a status row:

| Field | What it is |
|---|---|
| `reason` | Why the run returned no rows, why it stopped early, or, on a per-site miss, the detail behind `status` (for example `fetch_failed` carries `HTTP 403`). |
| `rowsFetched` | How many rows the source handed over before de-duplication and the cap. |
| `rowsReturned` | How many data rows are in the dataset (0 on an empty result; the count before the charge limit on STOPPED\_EARLY). |
| `rowsRemaining` | How many fetched rows were not returned (0 on an empty result). |

In the Console, the dataset's Overview view shows the columns a buyer reads first. The "All fields" view and every export carry the full contract. A null cell means that site did not publish the value, not that the field is missing.

### How much does it cost?

This Actor bills per **`profile-returned` event: $0.00496**. One `profile-returned` event is one site row written to your dataset whose `status` is `ok`.

A row reporting `no_imprint_found`, `fetch_failed`, `timeout`, `js_required`, `bad_url` or `search_failed` is pushed as `ITEM_STATUS`: visible in the dataset, never billed. You do not pay for this Actor's inability to read a site.

What that looks like in sites, at the measured 60% fill rate:

- **100 sites** → ~60 filled rows → **~$0.30**
- **1,000 sites** → ~600 filled rows → **~$2.98**

| Filled rows (`status: ok`) | `profile-returned` charge |
|---|---|
| 100 | $0.496 |
| 1,000 | $4.96 |
| 10,000 | $49.60 |

### Why this one

Measured against the most-used alternative on this shelf, run on 2026-09-12:

- **A row costs $0.00496.** The most-used alternative on this shelf charges $0.005 a row plus $0.04 to start.
- **A run costs $0.00005 to start.** The sellers on this shelf that bill a start fee charge $0.04–$0.08.
- **A miss is free and explained.** The row says why the site yielded nothing, and it is never billed.
- **The fill rate is measured, not promised: 60% of 100 sites this Actor had never seen, on the platform build a buyer's run uses.** No seller on this shelf publishes one.
- **Every row carries `imprint_url` and `pageUrl`.** The page each value was read from is on the row.

Search mode is **bring-your-own-key**: `searchQueries` with your own `searchApiKey` (and `searchCx` for Google) pay your own API quota and nothing on this side. When the Actor's configured key resolves the query instead, the run bills one **`search-page` event: $0.005**, one official search-API call at its list price. A query with no key returns a free `search_failed` row.

Apify also bills its own `apify-actor-start` event once per gigabyte of memory allocated: $0.00005 at this Actor's size. A single-domain lookup costs about half a cent total.

What is not charged:

- `ITEM_STATUS` miss rows, `PROFILE_NOT_FOUND` and `STOPPED_EARLY` status rows.
- Start URLs skipped and rows dropped by the exclusion list (`excluded` in RUN\_SUMMARY).
- Duplicate rows dropped on `pageId` before anything is pushed.
- The `RUN_SUMMARY` entry in the key-value store.

### How do I use German Impressum Contact Scraper?

1. Open the Actor's Store page and press **Try for free** / **Start**.
2. In **Start Urls**, list the sites to read: one URL per line, a site's root or its imprint page itself. Or skip the list entirely. Put terms in **Search queries** and the Actor resolves them to domains through an official search API.
3. Optionally lower or raise **Maximum items** (default 100). Adjust the **proxy** (datacenter by default; a site that blocks it is retried once through residential). Switch the **headless-browser fallback** off for cheap list runs. Fill **Exclude** with domains and names this run must not return.
4. Press **Start**. Rows land in the dataset as they are written. The key-value store's `RUN_SUMMARY` entry carries the run's counts when it finishes.

Example input:

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

Or search mode, with no list required:

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

Or run it through the Apify API:

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

### Input

| Input | Type | Default | What it does |
|---|---|---|---|
| `startUrls` | array | prefilled with `https://www.trigema.de` | The sites to read, one output row per entry. |
| `searchQueries` | array | — | Search terms resolved to domains through an official search API, then read like `startUrls` entries. |
| `searchEngine` | string | `brave` | `brave` (Brave Search API) or `google` (Google Custom Search JSON API; needs `searchCx`). |
| `searchApiKey` | string | — | Your own key for the chosen engine, required for `searchQueries`. No key is configured for you, so a query with no key returns a free `search_failed` row. |
| `searchCx` | string | — | Google programmable engine ID, required only for `searchEngine: "google"`. |
| `proxyConfiguration` | object | datacenter Apify proxy | How site requests are proxied. Datacenter first; a site that answers with a block page is retried once through residential. Configure RESIDENTIAL groups to proxy every request that way. |
| `useBrowser` | boolean | true | Render pages that answer plain requests with a JavaScript shell. Costs more memory and seconds per hit. |
| `maxItems` | integer | 100 | The most rows one run returns. Fetching and pushing stop there and the log notes the cap. Minimum 1. |
| `maxDomainsPerSearchQuery` | integer | 20 | The most domains one `searchQueries` entry may resolve to, so a broad query cannot starve the ones after it. |
| `exclude` | array | — | Domains and names this run must not return, one entry per line. A domain skips its start URLs before any fetch; a company or person name drops the row that carries it. Counted under `excluded` in RUN\_SUMMARY. |

#### `startUrls`

Each entry is `{ "url": "…" }` or a bare string; `https://` is added when it is missing. Give it a site's root. The Actor fetches that page. If it is not itself an imprint, the Actor finds the site's own imprint link (same domain, by URL or by link label) and reads that page instead. It follows up to two hops, and tries the contact page when the homepage carries no link. You can also hand it the imprint URL directly. One row comes back per start URL, whatever the outcome.

#### `exclude`

One entry per line. An entry that is a domain, like `dm.de` or a full URL, skips every start URL on that host or a subdomain of it, before any request is made. Any other entry is read as a name. A fetched row whose `company_name` or `managing_director` carries it is dropped: not pushed, not billed. The Actor's own exclusion list is applied the same way on every run. See Personal data for how a person or company joins it.

### Output

A run writes two kinds of things:

- **Data rows** in the default dataset, one per start URL. Each carries every contract field and `result: "ROW"`. The per-row `status` field tells a filled row (`ok`) from a site that yielded nothing (`no_imprint_found`, `fetch_failed`, `timeout`, `js_required`, `bad_url`, `search_failed`, `aborted`). A miss row carries `result: "ITEM_STATUS"` and is never billed. Rows are de-duplicated by `pageId`: two start URLs on the same domain return one row.
- **One status row**, only when there is something to report. A run that pushes no data rows ends with one `PROFILE_NOT_FOUND` row carrying `reason`, `rowsFetched`, `rowsReturned` and `rowsRemaining`, never an empty dataset that could mean "nothing found" or "it broke". A run cut short by the charge limit ends with a `STOPPED_EARLY` row saying how many rows were returned and how many were not. Neither is charged.

The key-value store's `RUN_SUMMARY` entry carries `rowsFetched`, `rowsPushed`, `rowsCharged`, `duplicatesDropped`, `excluded` and `stoppedEarly` for every run, alongside the input and start/finish timestamps. That is how a short run and a broken one are told apart.

A zero result is an answer, not silence: one `PROFILE_NOT_FOUND` row, uncharged, with `rowsFetched` telling you whether the source handed over anything at all.

### What can you do with the data?

- **Build a B2B contact list you can defend.** Every email and phone number comes off the company's own legally required disclosure page, not a third-party guess. The row keeps `imprint_url`, so you can show where each value was read.
- **Verify who is behind a site before you pay it.** `company_name`, `register_court` and `register_number` are the identifiers a court or a contracts team asks for. A supplier list of domains becomes a list of registered entities in one run.
- **Enrich a domain list into company records.** Feed a CRM export of websites in, get legal names, registered addresses and managing directors out, and join back on `domain`.
- **Find the real contact for outreach.** `status` separates sites that answered from sites that did not. The follow-up list is only the rows where there is something to follow up.

### Use German Impressum Contact Scraper with AI agents

```bash
claude mcp add --transport http apify "https://mcp.apify.com?tools=Pradio/imprint-contact"
```

Paste that line to give an MCP-capable agent this Actor as a tool. It can then hand the agent a list of sites and get imprint rows back.

### Personal data

A row can name a person: a managing director printed on a company's own legal-notice page. This section says plainly what the product is for, where the data comes from, and how removal works.

- **Purpose.** For identifying and verifying a company and reaching it through the business contact details it publishes. Not for building people databases, profiling, or marketing to individuals. You supply the domains. You are the controller of the rows your runs collect, you owe your own privacy notice, and each named site's own terms still apply to you.
- **Transparency and source.** Every row comes from the company's own legal-notice page, published under its duty to identify itself and its representatives. A row may carry a representative's name and role exactly as printed there, plus the company's published contact channels. Every row carries `imprint_url` and `pageUrl`, so the source of every value is visible. The publisher keeps no rows: runs and datasets live in your Apify account and belong to you.
- **Exclusion.** A person or company that wants its imprint left out can ask: open an issue on this Actor's Issues tab in the Apify Console. A request is honoured through the Actor's exclusion list, which every run reads at start. The domain is skipped before any fetch, and a fetched row naming the company or person is dropped. The `exclude` input applies the same mechanism to your own list. Every skip and drop is counted in `RUN_SUMMARY` under `excluded`.

### Release notes

- **0.2.34**: harder reads on app-shell sites — a render that returns only the shell waits for hydration once, then escalates to residential like a refused request does.
- **0.2.20**: the `exclude` input and the Actor's own exclusion list, read at the start of every run. Domains skip their start URLs; names drop the row that carries them. Counted under `excluded` in RUN\_SUMMARY.
- **0.2.17**: `country` now fills from the VAT-ID prefix when the page names no country. Register filings no longer appear inside director names.
- **0.2.15**: a per-query domain cap in search mode, so one broad query cannot starve the ones after it.
- **0.2.14**: the drawn listing icon.
- **0.2.12**: a separate `contact_person` field was reviewed and declined. Representatives appear only as printed on the page, inside `managing_director`.
- **0.2.6**: client-redirect shims, a tighter register-court boundary, name and address parsing polish.
- **0.2.4**: parser fixes measured on a second, larger run over German company sites; datacenter-first proxy with residential only when a site earns it; bring-your-own-key search made honest.
- **0.2**: unbilled misses, search mode, proxy escalation and the browser fallback.

### Limits

- **Coverage is measured on the platform, not total: 60%**. On 100 German company sites (DAX, MDAX and SDAX members, every one new to this build), run as the deployed build through the same proxied egress a buyer's run uses: 60 came back filled; 20% refused the fetch outright — bot walls, dead domains; 14% answered but exposed no notice; 6% timed out on the proxied path. Each such row says its `status` and is unbilled. Of the rows that resolved: company name 98%, register number 75%, register court 62%, VAT ID 42%, email 60%, directors 68%.
- **The vocabulary is German- and English-first.** A site that names its notice something outside the list under Features is reported `no_imprint_found`, not misread.
- **A field the page does not publish is null; nothing is invented.** `company_name` is null rather than guessed from the domain. `country` needs a named country or a VAT-ID prefix. `email` is the site's own-domain general mailbox or null, because a department's mailbox is not a contact.
- **One notice page per domain, and a refusal ends it.** The Actor reads a site's imprint page, plus the homepage to locate it, and nothing else on the host. A refused request is the answer, not a prompt for more attempts or a way around. An input with no usable URL fails the run loudly, so you always learn which of the two happened.

### Troubleshooting

**I passed 50 URLs and got 47 rows. Where did three go?**
Rows are de-duplicated by `pageId`, the site's domain. Two start URLs on the same domain, say `dm.de` and `www.dm.de/shop`, return one row between them. `RUN_SUMMARY.duplicatesDropped` says how many went that way. `maxItems` caps the total. `RUN_SUMMARY.excluded` counts any that your `exclude` list skipped.

**A row has `result: "ITEM_STATUS"` and almost every field is null.**
Look at `status` and `reason`. `no_imprint_found` means the site answered but no imprint page was found on it. `fetch_failed` means the page could not be retrieved (the HTTP status is in `reason`). `js_required` means the page only renders in a browser (turn `useBrowser` on; it is on by default). `timeout` means the host never answered. `bad_url` means the entry could not be parsed. These rows are returned free. Filter on `status: ok` or `result: ROW` when you only want billed rows.

**The dataset holds a single row saying `PROFILE_NOT_FOUND`.**
The run pushed no data rows, for example because it was aborted before the first site answered. `reason` and `rowsFetched` on that row say what the source handed over. The row is not charged. It is the difference between "nothing was there" and an empty dataset you would have to second-guess.

**The last row says `STOPPED_EARLY`.**
Your charge limit was reached mid-run. `rowsReturned` and `rowsRemaining` say how many fetched rows made it out and how many did not. Raise the limit and re-run to see the rest. Only the returned rows were billed.

### FAQ

**Can I use integrations with German Impressum Contact Scraper?**
Yes. Like every Apify Actor it plugs into Apify's integrations: Zapier, Make, Google Sheets, webhooks. It can also run on a schedule. A scheduled run over the same site list re-reads each imprint page and hands you the fresh rows.

**Can I use German Impressum Contact Scraper with the Apify API?**
Yes. The curl line under "How do I use" is the whole call: POST the input to `acts/Pradio~imprint-contact/run-sync-get-dataset-items` with your token and the dataset rows come back in the response. The `apify-client` libraries for JavaScript and Python wrap the same call.

**Can I use German Impressum Contact Scraper through an MCP server?**
Yes. The line in "Use with AI agents" registers it with an MCP-capable agent through Apify's hosted MCP server, no local install needed.

**Is it legal to scrape imprint pages?**
Imprint and Impressum pages are disclosures companies are legally required to publish. Each page is fetched only on a path its site's robots.txt allows, and there is a 400 ms gap between requests to the same host. That said, the contact data you collect is yours to handle lawfully. GDPR and local rules still apply to how you store and use it. The Personal data section above says what this product is for and how a company or person can ask to be left out.

### Report a problem

Something off, a site that parses wrong, a field that should be there? Open an issue on this Actor's Issues tab in the Apify Console. That is the fastest way to get it fixed, and the report is never a bother.

### Not affiliated

This Actor is not affiliated with, endorsed by, or sponsored by any of the sites it reads. Imprint and legal-notice pages belong to their operators. The tool reads what each site publishes and nothing more.

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

## `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. 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: no key is configured for you. If we ever configure one, each query bills one search-page event, charged only after the API answers.

## `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. Datacenter by default; a site that answers a datacenter IP with a block page is retried once through a residential proxy — the expensive class is earned by a block, not bought by default. Configure RESIDENTIAL groups yourself to proxy every request residentially.

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

When a site answers a plain request with a JavaScript shell, render it headlessly and read the rendered page. 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 — fetching and pushing stop there and the run log notes the cap.

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

The most domains one searchQueries entry may resolve to. 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 or managing\_director 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,
  "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"
        }
    ]
};

// 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" },
    ] }

# 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"
    }
  ]
}' |
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/YKUWHAPy3kmcxRcCG/openapi.json
