# Website Email Finder | Public Business Contacts (`atesen-software/website-email-contact-finder`) Actor

Find emails, phones and social profiles on company websites or a Google Maps lead list. One deduplicated row per site, with MX-checked emails. Results can contain personal data. Use them only with a lawful basis under the GDPR and similar laws.

- **URL**: https://apify.com/atesen-software/website-email-contact-finder.md
- **Developed by:** [Atesen Software](https://apify.com/atesen-software) (community)
- **Categories:** Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 website with contacts

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

**Legal notice:** this Actor reads public pages and can return personal data, including email addresses and phone numbers. Use the results only when you have a lawful basis under the GDPR and similar laws. You are responsible for opt-outs, anti-spam rules and how you store the data.

A **contact details scraper and email finder**: give it a list of company websites (or a **Google Maps Scraper** dataset). For each website you get **one clean row** with:

- 📧 **Emails**: from mailto links, page text, obfuscated forms (`info [at] acme (dot) com`), Cloudflare-protected addresses and structured data. Each email gets a **type** (role like `info@` or personal like `jan.novak@`), an **own-domain flag** and an **MX check** (can the domain receive mail?).
- 📞 **Phone numbers** in **E.164 format** (`+4930901820`), validated with Google's libphonenumber for the site's country. Each number is marked as coming from a **tel: link / structured data** (high confidence) or from **page text**.
- 🔗 **Social profiles**: LinkedIn, Facebook, Instagram, X/Twitter, YouTube, TikTok, Pinterest, Telegram, WhatsApp, Threads, GitHub. Share buttons and one-off links to other accounts (testimonials, embedded posts) are filtered out of the main columns.
- 🏢 **Company data** when the site publishes it: legal name, address, **VAT ID** (EU VAT numbers in imprints/footers), founding date, logo, country and language.

It crawls the **home page plus the best contact, imprint (Impressum), about and legal pages**, detected in 15+ languages (English, German, French, Spanish, Italian, Dutch, Portuguese, Polish, Czech, Turkish, Nordic languages and more). No browser, so it is fast and cheap.

### Why this actor

| | **Website Email & Contact Finder** | Typical "contact details scraper" |
|---|---|---|
| Price | **$3 per 1,000 websites with contacts**. Websites with nothing found, errors and duplicates are **free** | Charged **per page crawled** (about $2 per 1,000 pages, and up to 20 pages per website by default), so you can pay about $0.04 per website even when nothing is found |
| Output | **One merged row per website**: all emails, phones and socials together | One row per crawled page; you merge the rows yourself or run a second "merge" actor |
| Phones | **Validated E.164**, with the source of each number (link/structured data vs text) | Raw strings plus a separate "uncertain phones" list with dates, IDs and prices mixed in |
| Email quality | **MX check**, role/personal type, own-domain flag, junk filters (image names, Sentry/Wix IDs, placeholders, invalid TLDs) | Every match on the page |
| Company data | Address, legal name, **VAT ID**, country, language | Not included |
| Duplicates | Same domain twice (e.g. branches from Google Maps) → crawled and charged **once** | Crawled again |
| Problems | Clear status rows: `unreachable`, `blocked`, `parked_domain`, `invalid_url`, `social_profile`, `duplicate`, `no_contacts`. The run never fails because of one bad website | Missing rows or failed requests in the log |
| Memory and speed | **512 MB** default: about 40 websites in 10 seconds, no browser | 4 GB default, browser-based crawling |

### How to use it

1. Paste websites into **Websites** (domains are enough: `acme.com`), **or** paste the dataset ID of a Google Maps Scraper run into **Dataset IDs**.
2. Click **Start**.
3. Download the results as Excel, CSV or JSON, or open the **Contacts** view.

#### Example input

```json
{
  "websites": ["apify.com", "stripe.com", "www.bosch.de", "https://www.acme-handwerk.de/kontakt"],
  "maxPagesPerSite": 6,
  "verifyEmailDomains": true,
  "onlySameDomainEmails": false
}
```

#### Enrich Google Maps leads with emails and socials

Run Google Maps Scraper, copy the dataset ID of the run (Storage → Dataset ID), then:

```json
{
  "datasetIds": ["aBcDeFgHiJkLmNoPq"],
  "maxPagesPerSite": 6
}
```

Places without a website are skipped. Several branches with the same website are crawled once. The place name, place ID, address, phone and category are copied to each row under `source`, so you can join the results back.

#### Example output (one website)

```json
{
  "type": "site",
  "status": "ok",
  "input": "acme-werkzeuge.de",
  "domain": "acme-werkzeuge.de",
  "url": "https://www.acme-werkzeuge.de/",
  "companyName": "Acme Werkzeuge GmbH",
  "description": "Werkzeuge und Maschinen für Handwerk und Industrie seit 1950.",
  "country": "DE",
  "language": "de",
  "primaryEmail": "info@acme-werkzeuge.de",
  "emails": ["info@acme-werkzeuge.de", "vertrieb@acme-werkzeuge.de", "m.schmidt@acme-werkzeuge.de"],
  "primaryPhone": "+4930901820",
  "phones": ["+4930901820", "+493090182222"],
  "linkedin": "https://www.linkedin.com/company/acme-werkzeuge",
  "facebook": "https://www.facebook.com/acmewerkzeuge",
  "instagram": "https://www.instagram.com/acme.werkzeuge",
  "twitter": null,
  "youtube": "https://www.youtube.com/@acmewerkzeuge",
  "tiktok": null,
  "socials": { "linkedin": ["https://www.linkedin.com/company/acme-werkzeuge"], "facebook": ["https://www.facebook.com/acmewerkzeuge"] },
  "address": "Hauptstraße 1, 10115 Berlin, DE",
  "vatIds": ["DE123456789"],
  "emailDetails": [
    { "email": "info@acme-werkzeuge.de", "source": "mailto", "foundOn": "https://www.acme-werkzeuge.de/impressum", "type": "role", "sameDomain": true, "mxValid": true },
    { "email": "m.schmidt@acme-werkzeuge.de", "source": "text", "foundOn": "https://www.acme-werkzeuge.de/kontakt", "type": "personal", "sameDomain": true, "mxValid": true }
  ],
  "phoneDetails": [
    { "phone": "+4930901820", "source": "link", "foundOn": "https://www.acme-werkzeuge.de/" },
    { "phone": "+493090182222", "source": "text", "foundOn": "https://www.acme-werkzeuge.de/kontakt" }
  ],
  "pagesScraped": 5,
  "scrapedUrls": ["https://www.acme-werkzeuge.de/", "https://www.acme-werkzeuge.de/impressum", "https://www.acme-werkzeuge.de/kontakt", "..."],
  "scrapedAt": "2026-10-20T01:30:00.000Z"
}
```

#### Output fields

| Field | Description |
|---|---|
| `status` | `ok` (contacts found, charged), `no_contacts` (free). Problem rows have `type: "status"`: `invalid_input`, `invalid_url`, `social_profile`, `duplicate`, `unreachable`, `blocked`, `failed`, `not_html`, `parked_domain`, `invalid_dataset`, `no_websites` (all free) |
| `domain`, `url` | Registered domain and the final home page URL after redirects |
| `companyName`, `description`, `country`, `language` | From structured data, page meta tags and the domain |
| `primaryEmail`, `emails` | Best email first: own domain, then role addresses, then the most reliable source |
| `primaryPhone`, `phones` | E.164 numbers, high-confidence ones first |
| `linkedin`, `facebook`, `instagram`, `twitter`, `youtube`, `tiktok` | The company's main profile on each network, when we are confident it belongs to the company |
| `socials` | All profiles per network (up to 5) |
| `address`, `vatIds`, `organization` | Company data from schema.org markup and imprint pages |
| `emailDetails` | `email`, `type` (role/personal), `sameDomain`, `mxValid`, `source` (mailto, text, schema, cloudflare, html), `foundOn` |
| `phoneDetails` | `phone`, `source` (link = tel:/structured data, text), `foundOn` |
| `source` | The input record when the website came from a dataset (name, placeId, address, phone, category) |

The **Run summary** record (key-value store, `RUN_SUMMARY`) has the totals of the run.

### Pricing

Pay per event:

- **Website with contacts**: $0.003 ($3 per 1,000 websites) on the Free and Bronze plans, $0.0027 on Silver, $0.0025 on Gold and higher. Charged once per website that returned at least one email, phone or social profile, no matter how many pages were crawled.
- **Free**: websites with no contacts, unreachable, blocked or parked websites, invalid URLs, social media URLs and duplicates.
- A tiny actor start fee set by Apify ($0.00005).

You can set a maximum cost per run in the run options; the actor stops cleanly when it is reached.

### Tips

- **Imprint pages** (Germany, Austria, Switzerland) and **legal notice pages** (France, Italy, Spain) usually have the email, phone, address and VAT ID, and they are crawled first.
- Keep the default **512 MB** memory: it crawls about 10 websites in parallel. 256 MB works but is much slower.
- Raise **Max pages per website** to 10–15 for big corporate sites; keep 3–6 for small businesses (faster, same price).
- Use **Only emails on the company's own domain** for cleaner B2B lists.
- Websites protected by aggressive bot challenges are reported as `blocked` and not charged.

### Is it legal?

The actor only reads **publicly published business contact information** from company websites (the information companies publish so customers can reach them), and it does not log in anywhere. If you process personal data (for example personal email addresses of employees), you are responsible for complying with GDPR and similar laws: have a legitimate interest, respect opt-outs and local anti-spam rules. Use `onlySameDomainEmails` and the `type: "role"` flag to work only with generic company addresses.

### Related actors

- Google Maps Scraper → find businesses, then enrich them here with `datasetIds`.
- [SIRENE French company data](https://apify.com/atesen-software) → official SIREN/SIRET, VAT and financial data for French companies.

# Actor input Schema

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

Domains or URLs of the companies, one per line ("acme.com", "https://www.acme.de/kontakt"). Each website is crawled once: duplicates are skipped and not charged.

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

Same as Websites, in the request-list format used by other contact scrapers, so you can reuse existing inputs.

## `datasetIds` (type: `array`):

Datasets produced by other actors such as Google Maps Scraper (pick them, or paste dataset IDs in JSON input). The website is read from the website / url / domain field; name, place ID, address and phone are copied to the output under "source". Read-only access.

## `datasetUrlField` (type: `string`):

Field that contains the website, if it is not website / url / domain.

## `maxPagesPerSite` (type: `integer`):

Home page plus the best contact, imprint, about and legal pages (detected in 15+ languages). 6 is enough for most companies. The price is per website, not per page.

## `maxSites` (type: `integer`):

Stop after this many websites (0 = no limit). Useful for a quick test on a big dataset.

## `verifyEmailDomains` (type: `boolean`):

Drop emails whose domain cannot receive mail (no MX/A record). Removes typos and fake addresses.

## `onlySameDomainEmails` (type: `boolean`):

Keep only emails like info@acme.com for acme.com (drops gmail.com, agency and partner addresses).

## `includePhonesFromText` (type: `boolean`):

Besides tel: links and structured data, also return numbers written in the page text (validated with libphonenumber). Each phone in phoneDetails shows its source.

## `maxConcurrency` (type: `integer`):

How many websites to crawl at the same time.

## Actor input object example

```json
{
  "websites": [
    "apify.com",
    "stripe.com",
    "www.bosch.de"
  ],
  "maxPagesPerSite": 6,
  "maxSites": 0,
  "verifyEmailDomains": true,
  "onlySameDomainEmails": false,
  "includePhonesFromText": true,
  "maxConcurrency": 10
}
```

# Actor output Schema

## `contacts` (type: `string`):

No description

## `details` (type: `string`):

No description

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

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

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

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "websites": [
        "apify.com",
        "stripe.com",
        "www.bosch.de"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("atesen-software/website-email-contact-finder").call(input);

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

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

```

## Python example

```python
from apify_client import ApifyClient

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

# Prepare the Actor input
run_input = { "websites": [
        "apify.com",
        "stripe.com",
        "www.bosch.de",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("atesen-software/website-email-contact-finder").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "websites": [
    "apify.com",
    "stripe.com",
    "www.bosch.de"
  ]
}' |
apify call atesen-software/website-email-contact-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,atesen-software/website-email-contact-finder"
        }
    }
}
```

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/o3sAkSkceFXwFjJ9k/builds/jglKDlOfvIoHqXDLA/openapi.json
