# Verified Website Contact Finder 📧 No Placeholder Emails (`cleanfeed/verified-website-contacts`) Actor

Find real company emails, phone numbers and social profiles from any website. Pass domains or URLs; role and personal emails are returned separately, and placeholder addresses other scrapers return are filtered out. Bulk domains per run, export as JSON or CSV.

- **URL**: https://apify.com/cleanfeed/verified-website-contacts.md
- **Developed by:** [Yaniv van der Stigchel](https://apify.com/cleanfeed) (community)
- **Categories:** Lead generation, Automation, Integrations
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 domain enricheds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Finder — real contacts, not placeholders

Extract company emails, phone numbers and social profiles from any website.
Every address is labelled and filtered, so you get contacts you can actually use.

### What it does

- Extract emails from a list of websites
- Find company contact details in bulk
- Get emails and phone numbers from domains
- Enrich a domain list with contact info
- Scrape contact pages and Impressum details

### The problem it solves

Most contact scrapers return whatever looks like an email address. Tested
against real company homepages, that includes:

| Address | Where it came from |
|---|---|
| `damian.michelfelder@example.com` | Stripe's documentation samples |
| `billing@example.com` | Stripe's docs |
| `sara.baker@acme.com` | Mollie's marketing mockup |
| `zoya@lumos.com` | Mollie's mockup, invented brand |
| `logo@2x.png` | an image filename |

None of those are contacts. A scraper that returns them hands you a list that
looks like data and is not. **This one filters them out.**

### Every address is labelled

| Field | Meaning |
|---|---|
| `type: "role"` | `info@`, `sales@`, `support@` — a company contact point |
| `type: "personal"` | an individual's address |
| `confidence: "high"` | role address on the company's own domain |
| `confidence: "medium"` | personal address on the company's own domain |
| `confidence: "low"` | address on a different domain |
| `source` | the exact page it was found on |

Personal addresses are **off by default**. Role addresses are company contact
points; personal ones are individuals' data and carry GDPR obligations for you
as the data controller.

### Honest reporting

| `reason` | Meaning | Charged |
|---|---|---|
| — (`ok: true`) | Contacts found | Yes |
| `no-contacts-published` | Site reachable, publishes nothing | No |
| `domain-not-resolving` | The domain does not resolve — expired or mistyped | No |
| `site-blocked` | Site is up and refuses automated clients | No |
| `site-timeout` | No answer in the time budget — worth one retry | No |
| `site-tls-error` | Certificate or handshake failure, often expired | No |
| `site-error` | Server error on their side, may be temporary | No |
| `site-unreachable` | Anything else | No |

Many companies use contact forms and publish no address at all. You get told
that, and you are not charged for it.

**The failure codes are split on purpose.** Measured on a 400-domain sample
(3 September 2026): of the rows a single `site-unreachable` code used to cover,
35% did not resolve, 28% answered with a 4xx, 15% answered normally on retry,
11% failed on TLS and 7% timed out. Each of those wants a different response —
drop the dead domain, reach the blocked one another way, retry the flaky one —
and one code could not tell you which you had.

### Input

| Field | Required | Description |
|---|---|---|
| `domains` | yes | Domains or URLs |
| `includePersonal` | no | Default `false` |
| `maxConcurrency` | no | 1–20, default 5 |
| `proxy` | no | Optional |

### Output

Every row has the same fields whether it succeeded or failed, so you can
select columns without branching. Failed rows are never charged.

| Field | Type | Description |
|---|---|---|
| `success` | boolean | True when this row carries data. Failed rows are never charged. |
| `domain` | string | The website this row is about. |
| `roleEmails` | array | On-domain role addresses (info@, sales@, support@) — the company's own contact points. |
| `personalEmails` | array | On-domain addresses belonging to named individuals. Different reliability, and different legal handling under GDPR. |
| `otherDomainEmails` | array | Addresses on a different domain to the site — usually the agency that built it. Kept separate rather than presented as the company's contact. |
| `phones` | array | Phone numbers published on the site. |
| `socials` | array | Links to the company's social profiles. |
| `emailCount` | integer | Total addresses found, including off-domain ones. |
| `pagesFetched` | integer | How many pages of the site were crawled to produce this row. |
| `errorCode` | string | Machine-readable failure reason. Null on success. |
| `errorMessage` | string | Human-readable explanation of the failure. Null on success. |

#### Example — success

```json
{
  "success": true,
  "domain": "stripe.com",
  "roleEmails": [
    "info@stripe.com",
    "sales@stripe.com"
  ],
  "personalEmails": [
    "dana.olsen@stripe.com"
  ],
  "otherDomainEmails": [
    "hello@buildagency.example"
  ],
  "phones": [
    "+1 888 963 8955"
  ],
  "socials": [
    "https://twitter.com/stripe",
    "https://www.linkedin.com/company/stripe"
  ],
  "emailCount": 4,
  "pagesFetched": 6,
  "errorCode": null,
  "errorMessage": null
}
```

#### Example — failure

A failure carries the same fields, so nothing downstream has to branch.

```json
{
  "success": false,
  "domain": "contact-form-only.example",
  "roleEmails": null,
  "personalEmails": null,
  "otherDomainEmails": null,
  "phones": null,
  "socials": null,
  "emailCount": null,
  "pagesFetched": 5,
  "errorCode": "no-contacts-published",
  "errorMessage": "The site was reachable but publishes no email, phone, or social profile. Many companies only offer a contact form."
}
```

#### Error codes

- `domain-not-resolving`
- `site-blocked`
- `site-error`
- `site-tls-error`
- `site-timeout`
- `site-unreachable`
- `no-contacts-published`
- `unparseable-domain`
- `error`

### Use it for

- **Role emails from a website** — info@, sales@, support@, separated from personal
- **Company contact details** — emails, phones and social profiles per domain
- Lead lists and CRM enrichment from a domain list
- Sales prospecting without placeholder junk
- Partner and vendor research

### Use it from an AI agent (MCP)

This Actor is callable as a tool through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp), so Claude, ChatGPT, Cursor and VS Code can run it directly.

Add the server to your MCP client:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com",
      "headers": {
        "Authorization": "Bearer <YOUR_APIFY_TOKEN>"
      }
    }
  }
}
```

Then ask for what you want in plain language — for example *“find the contact details published on this company website”* — and the agent calls `cleanfeed/verified-website-contacts` with the right input. Every output field is described in the dataset schema, so the agent knows what it is getting back before it runs anything.

### Call it from code

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run = client.actor("cleanfeed/verified-website-contacts").call(run_input={
    "domains": ["https://stripe.com"],
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item["success"]:
        print(item)
```

#### JavaScript

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

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

const run = await client.actor('cleanfeed/verified-website-contacts').call({
    domains: ["https://stripe.com"],
});

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

#### cURL

```bash
curl -X POST "https://api.apify.com/v2/acts/cleanfeed~verified-website-contacts/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H 'Content-Type: application/json' \
  -d '{"domains": ["https://stripe.com"]}'
```

### Related actors

| If you need | Use |
|---|---|
| Company data from YouTube channels instead | [YouTube Channel Transcript Scraper](https://apify.com/cleanfeed/youtube-channel-transcript-downloader) |

### Limitations

- **Many companies publish no contact details at all**, using a form instead. Those return `errorCode: no-contacts-published`, which is an honest answer rather than a failure.
- **Off-domain addresses are separated, not discarded.** An address on another domain is usually the agency that built the site, so it lands in `otherDomainEmails` rather than being presented as the company's contact.
- **Placeholder addresses are filtered** — `example.com`, `acme.com`, `yourdomain.com` and asset filenames such as `logo@2x.png` that match an email pattern.
- Personal addresses are returned separately from role addresses and are off by default; under GDPR they carry different handling obligations.

### FAQ

#### How is this different from a regex over the page?

A regex also returns fake data. Placeholder domains (`example.com`, `acme.com`), asset filenames like `logo@2x.png`, and the web agency's own address all match an email pattern. Those are filtered or separated here.

#### What is the difference between role and personal emails?

Role addresses (`info@`, `sales@`, `support@`) are company contact points. Named addresses belong to individuals — different reliability, and different handling under GDPR. They are returned in separate fields, and personal ones are off by default.

#### Why did a domain return nothing?

Plenty of companies publish no address and use a contact form instead. That returns `errorCode: no-contacts-published`, which is a real answer — a guessed address that bounces costs you sender reputation.

#### What are otherDomainEmails?

Addresses found on the site but hosted elsewhere, typically the agency that built it. They are kept rather than dropped, but not presented as the company's own contact.

#### Can I process a list of domains?

Yes. Pass the whole list and tune `maxConcurrency`; one row is returned per domain, including the ones with nothing published.

### Notes

Only publicly published company contact details are collected, from pages the
company itself serves (homepage, contact, about, Impressum, legal). No login,
no purchased databases, no guessed addresses.

# Actor input Schema

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

Company websites to find contacts for: bare domains or full URLs. One record is returned per domain, including domains where nothing is published.

## `includePersonal` (type: `boolean`):

Off by default. Role addresses such as info@ and sales@ are company contact points. Personal addresses are individuals' data and carry GDPR obligations for you as the data controller.

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

Domains processed in parallel. Each domain fetches up to 6 pages.

## `proxy` (type: `object`):

Optional. Useful for large batches or region-restricted sites.

## Actor input object example

```json
{
  "domains": [
    "stripe.com",
    "mollie.com"
  ],
  "includePersonal": false,
  "maxConcurrency": 5,
  "proxy": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One row per domain: emails with role/personal type and confidence, phone numbers, social profiles, and the page each was found on.

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

Domains with contacts, domains publishing none, and unreachable domains.

# 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": [
        "stripe.com",
        "mollie.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("cleanfeed/verified-website-contacts").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": [
        "stripe.com",
        "mollie.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("cleanfeed/verified-website-contacts").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": [
    "stripe.com",
    "mollie.com"
  ]
}' |
apify call cleanfeed/verified-website-contacts --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cleanfeed/verified-website-contacts"
        }
    }
}

```

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/kWShPQrLlwLh9PeEM/builds/l7mjckcjFuU3e40km/openapi.json
