# Website Contact Extractor — Emails, Phones & Socials (`insight.solutions/website-contact-extractor`) Actor

Give it a list of website domains and get back business emails, phone numbers, LinkedIn and social profiles, the contact page URL and postal address, one row per domain. HTTP only, no browser, no login. You only pay for domains where something is found.

- **URL**: https://apify.com/insight.solutions/website-contact-extractor.md
- **Developed by:** [Insight Solutions](https://apify.com/insight.solutions) (community)
- **Categories:** Lead generation, Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 domain with contacts founds

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

## Website Contact Extractor — Emails, Phones & Socials

**Extract emails from a list of websites.** Give this Actor a list of domains and get back the business email addresses, phone numbers, LinkedIn and social profiles, contact-page URL and postal address for each one — one row per domain, as JSON, CSV or Excel.

It reads the sites' own public pages over plain HTTP: no browser, no proxy, no login, and **no email verification pings of any kind**. A typical domain costs one to four page fetches and finishes in a couple of seconds; 1,000 domains typically finish in a few minutes.

**You are only charged for domains where we actually find something**, plus a flat $0.0005 per run. Blocked sites, dead domains and sites with no published contact details cost you nothing.

### Try it in 30 seconds

```json
{ "domains": ["apify.com", "mozilla.org"] }
```

Bare domains, `www.` hosts and full URLs all work. A bare domain is resolved automatically across four permutations — `https://`, `https://www.`, `http://`, `http://www.` — so a site that only answers on one of them is still found.

Digging harder on sites that bury their details:

```json
{
  "domains": ["example-manufacturer.de", "example-agency.co.uk"],
  "maxPagesPerDomain": 8,
  "contactPathHints": ["/contact", "/kontakt", "/impressum", "/about", "/legal"]
}
```

### What we extract from each website (and what we never touch)

This is the important section, so it is near the top.

**We extract**

- **Role-based business addresses** — `info@`, `contact@`, `sales@`, `support@`, `hello@`, `careers@`, `press@`, `office@`, `admin@`, and their equivalents in German, French, Spanish, Italian, Portuguese and Polish (`kontakt@`, `vertrieb@`, `ventas@`, …).
- **Officer and owner mailboxes** — `owner@`, `founder@`, `ceo@`, `cto@`, `director@`, `manager@`, `geschaeftsfuehrer@`. These name a post, not a person: for an SMB list, `owner@plumbingco.example` is usually the address you wanted.
- **Non-personal generic addresses** — `team@`, `bookings@`, `orders@`, `enquiries@`. Business functions, not people.
- **Published business phone numbers**, normalised to E.164.
- **Company social profiles** — LinkedIn, X/Twitter, Facebook, Instagram, YouTube, GitHub.
- **The contact page URL**, the **postal address**, and whether the site has a **contact form**.

**We do not extract, by default**

- **Anything that is not a recognised business function.** The filter is an **allowlist**, not a name detector: an address survives only if its local part matches a known role or generic business word (trailing digits ignored, so `sales2@` and `shop2024@` still match). Everything else — `jane.doe@`, `anna.k@`, `sarah_jones88@`, `wolfgang@` — is classified `personal` and dropped before the row is written, and a given name anywhere in the local part beats a role word, so `anna.sales@` is personal too. Opt in with `includePersonalEmails`. The cost is deliberate: an unusual but genuine business mailbox (`kundenbetreuung@`) can be dropped too. Losing a business address costs you recall; emitting a natural person's address costs you a GDPR problem.
- **Contact details attached to a person in structured data.** If a page's JSON-LD marks an email as belonging to a `Person` — a blog author, a quoted employee — we never read it, *even when `includePersonalEmails` is on*. There is no switch for that one.
- **Names**, in any field, ever. And **`noreply@`, `donotreply@`, `bounce@`** and other addresses nobody can be reached at.

**We never** log in to anything, **never ping SMTP or call any email-verification API**, **never ignore `robots.txt`** (a site that disallows us gets `found: false, error: "robots_disallowed"` and is not charged), and never resell or retain your results.

### Use cases

- **Enrich a B2B lead list with contact details** — feed the domains from your CRM and get emails, phones and LinkedIn back, one row per company.
- **Get emails from a list of websites you already have** — a scraped directory, a conference exhibitor list, a Google Maps export.
- **Build an outbound list for an SMB vertical** — `owner@`, `info@` and a phone number is usually all a local-business campaign needs.
- **Find the LinkedIn and social profiles for a set of companies** — attributed to the company, not scraped from a share button.
- **Fill in missing phone numbers and postal addresses** for records you already hold, normalised to E.164.
- **Verify a supplier or partner list is still live** — dead, parked and blocked domains come back labelled, free of charge.

### How it compares

- **You pay per domain where contacts were found**, not per input row and not per page crawled. On a 1,000-domain list with an 85% hit rate that is 850 billable rows — the other 150 are in your dataset with a reason, at $0.00.
- **≈$1.50 per 1,000 domains with contacts**, where listings in this category run to $25 per 1,000 results.
- **GDPR posture by default, not as an afterthought.** Role-based allowlist, no names in any field, `Person`-marked JSON-LD never read, no SMTP probing, `robots.txt` honoured and reported.
- **One row per input domain, always** — including failures, with a machine-readable `error`. You can tell "we could not reach it" apart from "there was nothing there".
- **No headless browser.** That is a deliberate trade: sites that assemble an address in JavaScript are a miss, and in exchange the Actor costs a fraction of a browser-based crawler and runs in seconds.

### Input reference

| Field | Type | Default | What it does |
|---|---|---|---|
| `domains` | array of strings | — *(required)* | Domains or URLs, one per line. Email addresses and bare IPs are rejected with a diagnostic row and never charged |
| `maxPagesPerDomain` | integer 1–10 | `4` | Total pages fetched per domain, **including the home page**. The rest of the budget goes to the best-scoring contact/about/imprint links. Costs nothing extra — pricing is per domain, not per page |
| `contactPathHints` | array of strings | `/contact`, `/contact-us`, `/about`, `/imprint`, `/impressum`, `/legal` | Paths we prefer to follow. Prefix matching, case-insensitive. Anchor text mentioning contact/about/imprint/kontakt is followed regardless |
| `includePersonalEmails` | boolean | `false` | Also return person-shaped addresses. **Read the GDPR note below before enabling** |
| `extractPhones` | boolean | `true` | Parse `tel:` links, JSON-LD `telephone` and visible text; normalise to E.164 |
| `extractSocials` | boolean | `true` | Detect the six social platforms, normalised to the profile root |
| `requestTimeoutSecs` | integer 5–120 | `20` | Per-request timeout. Lower it to move faster through lists with many dead domains |
| `maxRunSecs` | integer 30–3600 | `240` | Wall-clock budget for the run. When reached, no further domains are started; the status message says how many were skipped, and skipped domains are never charged |

### Output reference

One row per input domain — always, including failures. The dataset is never empty.

```json
{
  "domain": "alpha-robotics.example",
  "inputUrl": "alpha-robotics.example",
  "resolvedUrl": "https://www.alpha-robotics.example/",
  "redirectChain": ["https://www.alpha-robotics.example/"],
  "httpStatus": 200,
  "pageTitle": "Alpha Robotics — Industrial automation",
  "emails": [
    { "address": "info@alpha-robotics.example", "type": "role", "foundOn": "https://www.alpha-robotics.example/" },
    { "address": "bookings@alpha-robotics.example", "type": "generic", "foundOn": "https://www.alpha-robotics.example/contact" }
  ],
  "phones": [{ "raw": "+44 20 7946 0958", "e164": "+442079460958", "foundOn": "https://www.alpha-robotics.example/" }],
  "socials": {
    "linkedin": "https://linkedin.com/company/alpha-robotics",
    "twitter": "https://twitter.com/alpharobotics",
    "facebook": null, "instagram": null, "youtube": null,
    "github": "https://github.com/alpha-robotics"
  },
  "contactPageUrl": "https://www.alpha-robotics.example/contact",
  "contactPageConfirmed": true,
  "addressText": "12 Kingsway, London, WC2B 6UN, GB",
  "hasContactForm": true,
  "pagesFetched": ["https://www.alpha-robotics.example/", "https://www.alpha-robotics.example/contact"],
  "found": true, "error": null,
  "source": "www.alpha-robotics.example",
  "sourceUrl": "https://www.alpha-robotics.example/",
  "scrapedAt": "2026-09-08T09:14:02.117Z"
}
```

**Field notes**

- **`emails[].type`** — `role` (a named business function), `generic` (business-flavoured but off the canonical role list), or `personal` (only when you opted in). Sorted role first. Page text is read the way a browser lays it out, so an address at the end of a link never fuses with the next label.
- **`phones[].e164`** — `null` when the number could not be validated. Numbers from a `tel:` link are kept even when they do not validate, because the site declared them; numbers from body text are kept only when they validate, so prices, years and postcodes do not end up in your CRM.
- **`socials`** — always all six keys, `null` where nothing was found. Share buttons, tweet-intent links and embedded videos are ignored. A profile is reported only with evidence the company owns it: JSON-LD `sameAs`, a handle matching the site's domain, or the same profile root on two pages. For LinkedIn, X/Twitter, YouTube and GitHub a link in the site's own header, footer or nav counts too; Facebook and Instagram keep the stricter rule.
- **`contactPageUrl` / `contactPageConfirmed`** — the best contact/imprint page identified, and whether we fetched it. **Only a confirmed page counts as a hit**, so a "Contact us" anchor that 404s never makes a domain billable.
- **`found`** — `true` when we found at least one email, phone, attributable social profile **or** a contact page we fetched. This is the flag that decides whether you are charged.
- **`error`** — `null` on success, otherwise `no_contacts`, `blocked`, `robots_disallowed`, `dns`, `timeout`, `tls`, `connection`, `not_html`, `too_large`, `invalid_input`, or `http_<status>`.

### Accuracy and coverage

**Roughly 85% of ordinary business websites yield at least one contact.** That is an estimate derived from the failure modes below, not a published benchmark — your hit rate depends on the list you feed in. SMB service businesses do better than enterprise SaaS companies that route everything through a form.

| What stops us | Roughly | What you see |
|---|---|---|
| Site publishes no address at all — contact form only | 5–10% | `hasContactForm: true`, `contactPageUrl` set; billable only if that page was fetched |
| Cloudflare / bot wall / WAF blocking datacenter IPs | 10–15% | `error: "blocked"` |
| Email assembled by JavaScript at runtime | a few % | address simply absent |
| Dead, parked or expired domain | varies by list | `error: "dns"` |
| `robots.txt` disallows us | rare | `error: "robots_disallowed"` |

**What we handle that simpler extractors miss:** Cloudflare `data-cfemail` encoding is decoded (arithmetic on served markup — we execute no JavaScript); `name [at] example [dot] com` and its `(at)` / `{at}` variants are de-obfuscated; JSON-LD `Organization` and `ContactPoint` blocks are parsed, including `@graph` and nested `publisher`; `mailto:` links with `?subject=`, display names and multiple recipients parse correctly; contact pages are followed by path and anchor text in six languages; non-Latin imprint pages work, with no ASCII assumptions.

**When a run reports FAILED.** If at least one domain yielded contacts, or every domain answered and simply published nothing, the run **SUCCEEDS** — "no contacts published" is a correct answer, with a free row for it. Only when **every** domain failed on DNS, a timeout, TLS, a refused connection or a bot wall, and nothing was extracted, does the run finish **FAILED** — reporting that as a success would hide a broken Actor, or a blocked egress IP, behind a dataset full of diagnostics. Every diagnostic row is still written, and no `domain-with-contacts` event is charged on it.

### Pricing

| Event | Free | Bronze | Silver | Gold |
|---|---|---|---|---|
| `actor-start` — once per run, after input validation | $0.0005 | $0.0005 | $0.0005 | $0.0005 |
| **`domain-with-contacts`** — per domain, only when contacts are found | **$0.0015** | **$0.0015** | **$0.0011** | **$0.0008** |

**≈ $1.50 per 1,000 domains with contacts.** Nothing for the misses.

**Worked example.** One run over 1,000 domains on the Free or Bronze tier, of which 850 yield at least one contact:

```
actor-start            1 × $0.0005 = $0.0005
domain-with-contacts 850 × $0.0015 = $1.2750
                                     ───────
                                     $1.2755
```

The 150 domains that were blocked, dead or empty are in your dataset, and cost $0.00.

You are **not** charged when the site is behind a bot wall (`blocked`), the domain does not resolve (`dns`), `robots.txt` tells us not to, the request times out or TLS fails, the input was an email address or an IP (`invalid_input`), the site loaded fine and simply publishes no contact details (`no_contacts`), we saw a "Contact us" link but the page budget ran out before fetching it, or the run hit `maxRunSecs` or your run-cost limit before reaching that domain. The one flat charge is `actor-start`, billed once per run and only after your input has been validated — a run with no usable domain in it exits before charging anything at all.

### Integrations and use from code

One JSON object in, one flat array out. The **Integrations** tab pushes results straight to Slack, a webhook, Zapier, Make, Google Sheets, Snowflake or BigQuery, and Apify Schedules run it unattended — contact details change slowly, so monthly is usually plenty.

```bash
curl -X POST "https://api.apify.com/v2/acts/insight.solutions~website-contact-extractor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domains":["apify.com","mozilla.org"],"maxPagesPerDomain":4}'
```

```python
## pip install apify-client
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("insight.solutions/website-contact-extractor").call(run_input={
    "domains": ["apify.com", "mozilla.org", "example-agency.co.uk"],
    "maxPagesPerDomain": 6,
})

for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    if row["found"]:
        emails = ", ".join(e["address"] for e in row["emails"])
        print(row["domain"], emails, row.get("socials", {}).get("linkedin"), sep=" | ")
```

### GDPR / CCPA notes for operators

Read this before you switch `includePersonalEmails` on.

- **The default output is business contact data.** Role-based addresses like `info@` and `sales@` identify a function, not a person — the least contentious category of contact data there is, and most EU regulators treat them as such.
- **`includePersonalEmails` changes that.** `jane.doe@company.com` identifies a natural person and is personal data under the GDPR and the CCPA/CPRA, regardless of having been published on a public web page.
- **If you enable it, you are the controller** — not Apify, and not this Actor's developer. You need a lawful basis (usually legitimate interest, with a balancing test you can produce on request), you owe the Article 13/14 notice at first contact, and you must honour access, rectification, erasure and objection requests.
- **Marketing to these addresses is a separate question.** The GDPR governs whether you may hold the data; ePrivacy and national law govern whether you may email it, and several member states require prior consent for B2B email marketing regardless of your GDPR basis. CAN-SPAM is more permissive but still requires accurate headers and a working opt-out.
- **We do not verify deliverability**, so treat every address as unconfirmed.

Nothing here is legal advice. If you are running outbound at scale into the EU, get some.

### FAQ

**Do you check whether the email address actually works?**
No, and we will not add it. Verification means connecting to the recipient's mail server and probing for a mailbox — a different legal and technical posture, and what gets IP ranges blacklisted. We report exactly what the website publishes.

**Why did a site come back `blocked`?**
Its WAF refused a datacenter IP, or served a bot-check interstitial instead of the page. We detect both — including the awkward case where the interstitial is served with HTTP 200 — and report `blocked` rather than a row full of Cloudflare boilerplate. You are not charged.

**Why is `found` true when `emails` is empty?**
We found a phone number, a social profile attributable to the company, or a contact page we actually fetched. Any one is a usable route to the company. Filter on `emails.length > 0` if you only want rows with an email.

**My run says FAILED but there are rows in the dataset.**
Every domain failed to load — DNS, timeout, TLS, refused connection or a bot wall — and nothing was extracted. The rows are the diagnostics, with the reason in `error`. No domain was charged. If a big, varied list fails this way, something upstream is wrong.

**Can I feed it URLs from a sitemap or a crawl?**
Yes. Full URLs are used as given, fetched directly rather than through the permutation ladder. Duplicates are not merged, so de-duplicate your list first if you do not want to pay twice for the same company.

**Does it follow links beyond the pages I allowed?**
No. `maxPagesPerDomain` is a hard cap on total fetches per domain, home page included, and we follow only same-registrable-domain links that score as contact-ish. There is no recursive crawl.

### Legal

- **Public pages only.** Everything this Actor reads is served to any anonymous visitor. We never authenticate, never supply cookies or session tokens, and never accept credentials as input.
- **We honour `robots.txt`** on every host we touch, and report a refusal as a result rather than working around it. **No SMTP probing, no verification APIs.**
- **We are not a data broker.** This Actor holds no database and sells no list. It reads pages on demand and writes the result to *your* dataset on *your* account. We retain nothing.
- **Not affiliated with, endorsed by or sponsored by** any website you point it at, nor by Cloudflare, LinkedIn, X/Twitter, Meta, Google, YouTube or GitHub. Platform names are used descriptively, to say which link we detected.
- **You are responsible for how you use the output.** Compliance with the GDPR, the CCPA, ePrivacy, CAN-SPAM and applicable marketing law sits with you as the operator.

### Our other Actors

HTTP-only data Actors — no browsers, no proxies, charge-on-success.

| Actor | What it does |
|---|---|
| [Career Site Jobs API](https://apify.com/insight.solutions/ats-jobs-api) | Every open role from Greenhouse, Lever, Ashby, Workable and 10+ other ATS platforms, normalized |
| [New Job Postings Monitor](https://apify.com/insight.solutions/job-postings-monitor) | Only the roles that opened, closed or changed since your last run — hiring signals for sales and recruiting |
| [SEC EDGAR API](https://apify.com/insight.solutions/sec-edgar-api) | SEC filings, normalized XBRL financials and full-text search, by ticker or CIK |
| [Wayback Machine Toolkit](https://apify.com/insight.solutions/wayback-toolkit) | Archived URLs, archived page text, and a diff of one page between two dates |

# Actor input Schema

## `domains` (type: `array`):

One domain or URL per line, e.g. <code>apify.com</code>, <code>www.example.co.uk</code> or <code>https://example.com/about</code>. Bare domains are resolved automatically over https/http and with/without <code>www.</code>. Email addresses and bare IP addresses are rejected with a diagnostic row and are never charged.

## `maxPagesPerDomain` (type: `integer`):

Total pages fetched per domain, including the home page. The remaining budget is spent on the most promising contact/about/imprint links found on the home page. Raise it for sites that bury contact details; every extra page costs you nothing extra — pricing is per domain, not per page.

## `contactPathHints` (type: `array`):

Internal paths we prefer to follow when looking for contact details. Matching is case-insensitive and prefix-based, so <code>/contact</code> also matches <code>/contact-us/</code>. Anchor text containing contact/about/imprint/impressum/kontakt is also followed even if the path is not listed here.

## `includePersonalEmails` (type: `boolean`):

⚠️ <b>Leave this OFF unless you have a lawful basis.</b> By default we return only role-based business addresses (<code>info@</code>, <code>sales@</code>, <code>careers@</code>, <code>support@</code>, …) and non-personal generic addresses. Turning this on also returns addresses that look like a named individual (<code>jane.doe@</code>, <code>j.doe@</code>). Those are <b>personal data</b> under the GDPR and the CCPA. If you enable it, <b>you</b> — not Apify and not this Actor's developer — are the data controller for that processing, and you are responsible for having a lawful basis, for honouring access/erasure requests, and for the transparency notices required by Articles 13–14 GDPR.

## `extractPhones` (type: `boolean`):

Parse <code>tel:</code> links, JSON-LD <code>telephone</code> values and visible text, then normalise to E.164 using the country hinted by the site's postal address or its top-level domain.

## `extractSocials` (type: `boolean`):

Detect LinkedIn, X/Twitter, Facebook, Instagram, YouTube and GitHub profile links, normalised to the profile root. Share buttons and intent links are ignored.

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

Per-request timeout. Lower it to move faster through large lists of dead domains; raise it for slow servers.

## `maxRunSecs` (type: `integer`):

Wall-clock budget for the whole run. When it is reached, no further domains are started; every domain already finished is in your dataset and the run ends SUCCEEDED with a status message saying how many were skipped. Skipped domains are never charged. Raise it for lists of more than a few thousand domains.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "mozilla.org"
  ],
  "maxPagesPerDomain": 4,
  "contactPathHints": [
    "/contact",
    "/contact-us",
    "/about",
    "/imprint",
    "/impressum",
    "/legal"
  ],
  "includePersonalEmails": false,
  "extractPhones": true,
  "extractSocials": true,
  "requestTimeoutSecs": 20,
  "maxRunSecs": 240
}
```

# Actor output Schema

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

One row per input domain with the business emails, phone numbers, social profiles and contact page found. Delivered as JSON items in the default dataset.

# 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 = {
    "domains": [
        "apify.com",
        "mozilla.org"
    ],
    "maxPagesPerDomain": 4,
    "contactPathHints": [
        "/contact",
        "/contact-us",
        "/about",
        "/imprint",
        "/impressum",
        "/legal"
    ],
    "includePersonalEmails": false,
    "extractPhones": true,
    "extractSocials": true,
    "requestTimeoutSecs": 20,
    "maxRunSecs": 240
};

// Run the Actor and wait for it to finish
const run = await client.actor("insight.solutions/website-contact-extractor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "domains": [
        "apify.com",
        "mozilla.org",
    ],
    "maxPagesPerDomain": 4,
    "contactPathHints": [
        "/contact",
        "/contact-us",
        "/about",
        "/imprint",
        "/impressum",
        "/legal",
    ],
    "includePersonalEmails": False,
    "extractPhones": True,
    "extractSocials": True,
    "requestTimeoutSecs": 20,
    "maxRunSecs": 240,
}

# Run the Actor and wait for it to finish
run = client.actor("insight.solutions/website-contact-extractor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "domains": [
    "apify.com",
    "mozilla.org"
  ],
  "maxPagesPerDomain": 4,
  "contactPathHints": [
    "/contact",
    "/contact-us",
    "/about",
    "/imprint",
    "/impressum",
    "/legal"
  ],
  "includePersonalEmails": false,
  "extractPhones": true,
  "extractSocials": true,
  "requestTimeoutSecs": 20,
  "maxRunSecs": 240
}' |
apify call insight.solutions/website-contact-extractor --silent --output-dataset

```

## MCP server setup

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

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/4PyPpjKwM3FbtnNbQ/builds/qcIhoSeikMi8xcwDY/openapi.json
