# Website Contact & Email Scraper (`apt_marble/website-contact-email-scraper`) Actor

Paste a list of company websites and get a contact record for each: work email, phone, postal address, social profiles and company details. Addresses belonging to the company itself are marked as such, and nothing is ever guessed.

- **URL**: https://apify.com/apt\_marble/website-contact-email-scraper.md
- **Developed by:** [Hamza](https://apify.com/apt_marble) (community)
- **Categories:** Lead generation, Other
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.00 / 1,000 website processeds

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 & Email Scraper

Paste a list of company websites and get back a clean contact record for each one: the work
email address, the phone number, the postal address, the company's social profiles and its own
details. Built for sales and marketing teams who already have a list of target accounts and
need the contact details filled in — and for agencies turning a prospect spreadsheet into
something they can actually send.

No account, login or connected profile is needed. Paste your domains and press start.

### What you get

**Contact information**

- Work email address, with the ones belonging to the company itself marked as such
- Up to five additional addresses found on the same website
- Phone number, in both the original and full international format, with numbers the company
  published as dialable marked separately from numbers merely printed on a page
- Postal address: street, city, region, postal code

**Company information**

- Company name and, where published, the registered legal name
- Website and domain
- Short description and logo
- VAT identifier where the company publishes one

**Social profiles**

- Company profile, plus X/Twitter, Facebook, Instagram, YouTube and GitHub where linked

**Named people** (optional)

- Names and job titles published on the website, each marked as either **stated** by the
  company or **worked out** from the page, so you always know which is which

**Provenance on every row**

- Which page each contact detail came from (contact page, legal notice, team page, home page)
- How many pages were read, and whether the row is contactable at all

### What you can do with it

- Turn a list of target-account domains into a sendable list, in one run.
- Enrich a CRM export: match on domain, fill in the blanks.
- Qualify inbound signups — resolve a company domain into who they are and how to reach them.
- Build an agency prospect list from a competitor-customer or industry list you already have.
- Find the right inbox for a partnership or supplier approach, instead of a contact form.
- Recover contact details from websites that deliberately obscure them.
- Chain it after any list-building actor: it upgrades a list of companies into a list of contacts.

### Sample record

```json
{
  "companyName": "Example Communications GmbH",
  "companyLegalName": "Example Communications GmbH",
  "companyDomain": "example-agency.com",
  "companyWebsite": "https://example-agency.com",
  "email": "info@example-agency.com",
  "emailType": "team",
  "emailIsOwnDomain": true,
  "emailFoundOn": "legal notice",
  "additionalEmails": ["press@example-agency.com"],
  "phone": "+49 211 635555-0",
  "phoneE164": "+492116355550",
  "phoneType": "published",
  "addressLine1": "Beispielstraße 74",
  "addressFormatted": "Beispielstraße 74, 40219 Düsseldorf",
  "city": "Düsseldorf",
  "postalCode": "40219",
  "addressSource": "page text",
  "companyLinkedinUrl": "https://linkedin.com/company/example-agency",
  "twitterUrl": "https://twitter.com/exampleagency",
  "vatId": "DE219349391",
  "pagesRead": 3,
  "pagesUsed": ["home page", "contact page", "legal notice"],
  "hasContact": true,
  "collectedAt": "2026-08-18T18:34:11.902Z"
}
```

### Input reference

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `domains` | list | 3 examples | The company websites to look up. A domain (`acme.com`) or a full address both work. Paste as many as you like. |
| `maxPagesPerDomain` | integer | `4` | Pages to read per website. Four covers the pages that carry contact details on almost every site. Maximum 8. |
| `includeSocialProfiles` | boolean | `true` | Return the company's social profile links. |
| `includePeople` | boolean | `false` | Also return people named on the website with their job titles. |
| `requireContact` | boolean | `false` | Skip websites that publish no email and no phone number, so every row is actionable. |
| `country` | string | empty | Two-letter country code used to put locally-written phone numbers into international format. |
| `maxDomains` | integer | `1000` | Stop after this many websites. Maximum 50,000. |
| `parallelDomains` | integer | `5` | How many websites to read at once. Maximum 15. |

### Output fields

| Field | Meaning |
| --- | --- |
| `companyName`, `companyLegalName` | The company, and its registered name where published |
| `companyDomain`, `companyWebsite` | The domain you supplied, normalised, and the site read |
| `description`, `logoUrl` | Company description and logo where published |
| `email` | The best contact address found |
| `emailType` | `personal` for a named individual, `team` for a shared inbox |
| `emailIsOwnDomain` | Whether the address belongs to this company rather than a third party |
| `emailFoundOn` | Which page it came from |
| `additionalEmails` | Up to five more addresses found |
| `phone`, `phoneE164` | The number as published, and in international format |
| `phoneType` | `published` when the site marked it dialable, `listed` when it was printed on a page |
| `additionalPhones` | Up to three more numbers |
| `addressLine1`, `addressFormatted`, `city`, `region`, `postalCode`, `country` | The postal address |
| `addressSource` | `structured` when the company published it as data, `page text` when read from the page |
| `companyLinkedinUrl`, `twitterUrl`, `facebookUrl`, `instagramUrl`, `youtubeUrl`, `githubUrl` | Social profiles |
| `vatId` | VAT identifier where published |
| `people`, `peopleCount` | Named people with job titles, when requested |
| `pagesRead`, `pagesUsed` | How many pages were read, and what they were |
| `hasContact` | Whether the row carries any way to reach the company |
| `sourceUrl`, `collectedAt` | Where the record came from, and when |

### What coverage to expect

Measured on a live run over 12 real websites — a mix of large brands, marketing agencies,
German companies and one deliberately invalid domain:

| Field | Coverage |
| --- | --- |
| Company identified | 100% of reachable websites |
| Phone number | 100% |
| Email address | 73% |
| …of those, belonging to the company itself | 100% |
| Social profile | 82% |
| Postal address | 45% |
| Websites that could not be read at all | 1 of 12 |

Coverage varies with the kind of company. Agencies and small businesses publish contact
details freely; large brands often publish a form instead of an address. Postal addresses are
the least consistently published field, which is why 45% is what is promised rather than
something rounder.

### Pricing

You pay per result:

- **Website processed** — one contact record added to your dataset.
- **Verified contact** — charged only when the row carries an email address belonging to the
  company itself, or a phone number the company published as dialable. You are not charged
  this for a row where the only address found belongs to somebody else.

A 1,000-domain list where about three quarters yield a verified contact costs roughly **$16**.
A 12-domain test costs a few cents.

### Limits & what this actor cannot do

- **Not every website publishes contact details.** Some publish only a form. Where nothing is
  published, the row still identifies the company and is marked as not contactable rather than
  being padded with a guess.
- **Email addresses are the ones the company published** — this actor does not guess or
  construct addresses, and never presents an invented address as real. If you need addresses
  worked out for named people, that is a different job and a different actor.
- **Addresses belonging to third parties are not promoted.** A legal notice names regulators,
  arbitration bodies and the agency that built the site, each with their own contact details.
  Those are excluded from the main fields where they can be identified, and any address that
  does not belong to the company is flagged rather than presented as its own.
- **Postal address coverage is the weakest field, at under half of websites.** Where the
  company publishes it as structured data it is reliable; where it is read from page text, the
  `addressSource` column tells you so.
- Some websites cannot be read at all — around one in ten in practice, and more among small
  businesses. Those are reported individually with the reason, never silently returned empty.
- Websites that ask not to be read are respected and reported as such.
- Details are a snapshot at the moment of collection. Companies change addresses, phone
  numbers and staff.
- Named people are only returned when a job title is published alongside the name, and are
  labelled as stated or inferred. A name with no role attached is not returned as a contact.
- Speed depends on the size of your list; no fixed throughput is promised.
- **You are responsible for using this data lawfully.** Some of what this actor returns is
  personal data. You are the data controller for anything you collect, and you must have a
  lawful basis for contacting people and honour opt-outs — including under GDPR, CCPA,
  CAN-SPAM and PECR. Business-to-business outreach is regulated differently in different
  markets; check the rules that apply to yours before you send.

### FAQ

**Do I need an account or a login for anything?**
No. There is nothing to connect and nothing to sign in to.

**Are these email addresses guessed?**
No. Every address returned was published on the company's own website. Nothing is constructed
from a name-and-domain pattern, and nothing is presented as real unless it was found.

**Why is the email sometimes a shared inbox like `info@`?**
Because that is often the only address a company publishes, and it is a real, monitored inbox.
The `emailType` column tells you whether you got a named individual or a shared inbox, so you
can filter. Unmonitored mailboxes such as `no-reply@` are never returned.

**Some contact pages hide their address behind a script. Do you still find it?**
Usually, yes. Addresses that are obscured rather than absent are recovered, and the
`emailFoundOn` column tells you which page each one came from.

**Can I run this over a list of 10,000 domains?**
Yes. Raise `maxDomains` and leave the rest as it is. Very large lists are paced automatically.

**Can I schedule it?**
Yes. A common pattern is a weekly run over newly-added CRM accounts.

**What if a website blocks it?**
That website is reported individually with the reason, and you are not charged a verified
contact for it. It is never returned as though the company had no contact details.

# Actor input Schema

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

The company websites to look up. Enter a domain like "acme.com" or a full address — both work. Paste as many as you like.

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

How many pages to read on each website before moving on. Four covers the pages that carry contact details on almost every site; raising it finds a little more on large sites and costs more time.

## `includeSocialProfiles` (type: `boolean`):

Return the company's social profile links alongside its contact details.

## `includePeople` (type: `boolean`):

Also return people named on the website with their job titles, where the site publishes them. Each person is marked as either stated by the company or worked out from the page.

## `requireContact` (type: `boolean`):

Skip websites that publish no email address and no phone number, so every row you receive is actionable.

## `country` (type: `string`):

Two-letter country code such as "US", "GB" or "DE". Used to put locally-written phone numbers into international format. Numbers already written internationally are unaffected.

## `maxDomains` (type: `integer`):

Stop after this many websites.

## `parallelDomains` (type: `integer`):

How many websites to read at the same time. Leave as it is unless you are processing a very large list.

## Actor input object example

```json
{
  "domains": [
    "stripe.com",
    "figma.com",
    "sipgate.de"
  ],
  "maxPagesPerDomain": 4,
  "includeSocialProfiles": true,
  "includePeople": false,
  "requireContact": false,
  "country": "",
  "maxDomains": 1000,
  "parallelDomains": 5
}
```

# Actor output Schema

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

Every lead record this run produced.

## `runSummary` (type: `string`):

What this run collected, and anything it could not reach.

# 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",
        "figma.com",
        "sipgate.de"
    ],
    "maxPagesPerDomain": 4,
    "includeSocialProfiles": true,
    "includePeople": false,
    "requireContact": false,
    "country": "",
    "maxDomains": 1000,
    "parallelDomains": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("apt_marble/website-contact-email-scraper").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",
        "figma.com",
        "sipgate.de",
    ],
    "maxPagesPerDomain": 4,
    "includeSocialProfiles": True,
    "includePeople": False,
    "requireContact": False,
    "country": "",
    "maxDomains": 1000,
    "parallelDomains": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("apt_marble/website-contact-email-scraper").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",
    "figma.com",
    "sipgate.de"
  ],
  "maxPagesPerDomain": 4,
  "includeSocialProfiles": true,
  "includePeople": false,
  "requireContact": false,
  "country": "",
  "maxDomains": 1000,
  "parallelDomains": 5
}' |
apify call apt_marble/website-contact-email-scraper --silent --output-dataset

```

## MCP server setup

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

```

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/62q8dKDmPdEUfoZie/builds/nx7HZSyddmwaBKvbp/openapi.json
