# Contact Details Scraper - Website Emails, Phones & Socials API (`rel8ble/website-contact-extractor`) Actor

Contact details scraper and website email extractor. Input: company websites or domains. One result = one website: emails, phones, LinkedIn/Facebook/Instagram/X/YouTube/TikTok links, address, company name, contact page. Reads homepage + contact/imprint/about pages. $1.20 per 1,000 sites.

- **URL**: https://apify.com/rel8ble/website-contact-extractor.md
- **Developed by:** [Giovanni Rich](https://apify.com/rel8ble) (community)
- **Categories:** Lead generation, Marketing
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 results

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?

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

## Contact Details Scraper - Website Emails, Phones & Socials API

This **contact details scraper** is a **website email extractor**, phone number finder and **social media link scraper** for **B2B lead generation**: give it a list of company websites or domains and get **one clean row per website** with every business email, phone number (international format), LinkedIn company page, Facebook, Instagram, X/Twitter, YouTube, TikTok, Pinterest, GitHub, WhatsApp and Telegram link, the company name, postal address (from structured data), logo and the URL of the contact page.

It reads the homepage plus the most contact-like pages it can find (contact, imprint / Impressum, about, support, locations) over plain HTTP, with **no headless browser**, so 1,000 websites finish in minutes. Unlike per-page contact scrapers, you pay **per website**, no matter how many pages it reads.

### What you get

One dataset item per website:

- **Company**: `companyName` (from schema.org data, `og:site_name` or the page title), `title`, `description`, `language`, `country` (from the domain ending), `logo`, `address` (from schema.org `PostalAddress` when the site publishes it).
- **Emails**: `email` (the best one: same domain and role inboxes like info@ / sales@ first), `emails` (all, ranked), `emailSources` (where each was found: `mailto`, `json-ld`, `cloudflare`, `text` plus the page path). Cloudflare-protected addresses are decoded, and `info [at] acme [dot] com` style obfuscation is read.
- **Phones**: `phone`, `phones` (international format, e.g. `+49 9831 5050`) and `phonesE164` (`+4998315050`), validated with libphonenumber. Clickable `tel:` links and structured data come first.
- **Social profiles**: `linkedin`, `facebook`, `instagram`, `twitter`, `youtube`, `tiktok`, `pinterest`, `github`, `threads`, `whatsapp`, `telegram`, `discord`, `yelp`, `trustpilot` (the site-wide profile, preferring links in the homepage header/footer), plus `socialProfiles` with every profile found per network. Share buttons, posts and personal LinkedIn profiles are ignored.
- **Crawl info**: `contactPageUrl`, `pagesCrawled`, `pageUrls`, `finalUrl` (after redirects), `status` (`ok` or `failed`) and `scrapedAt`.

Websites that cannot be loaded (domain does not exist, server down, strict bot protection) get a row with `status: "failed"` and an `error`. **Those rows are free.** A `RUN_SUMMARY` record lists totals and fill rates.

### Sample output

Real rows (one row per website; hetzner and apify from `sample-output.json`, sennheiser and kinsta from a 67-site cloud run):

| domain | companyName | email | phone | linkedin | pagesCrawled |
|---|---|---|---|---|---|
| hetzner.com | Hetzner | info@hetzner.com | +49 9831 5050 | https://www.linkedin.com/company/hetzner-online | 6 |
| apify.com | Apify Technologies s.r.o. | hello@apify.com | - | https://linkedin.com/company/apify | 6 |
| sennheiser.com | Sennheiser | contact@sennheiser.com | +49 5130 6000 | https://www.linkedin.com/company/sennheiser | 6 |
| kinsta.com | Kinsta Inc. | contact@kinsta.com | +1 888 610 2915 | https://www.linkedin.com/company/kinsta | 6 |

### Use cases

- **Enrich a lead list**: you have company domains from Google Maps, LinkedIn, a trade-show list or a CRM export; add emails, phones and social links.
- **Sales prospecting**: find the general inbox, sales line and LinkedIn company page for every account before outreach.
- **Agency and SEO outreach**: collect contact emails and social handles for a list of sites.
- **Data hygiene**: re-check that the emails and phones in your CRM are still the ones on the website.
- **AI agents**: one call turns a list of domains into structured, deduplicated contact data.

### Pricing

**$1.20 per 1,000 websites** (pay per result; failed requests and failed websites are never charged). One result = one website, however many pages were read. Per-page contact scrapers charge for every page they open, about 6 per site: the most-used one bills $2.00 per 1,000 pages (about $12 per 1,000 sites) plus extra events per lead and social profile, and the pay-per-website alternatives in the top results charge $1.25-$20.00 per 1,000.

| You scrape | You pay |
|---|---|
| 100 websites | $0.12 |
| 1,000 websites | $1.20 |
| 10,000 websites | $12.00 |
| 100,000 websites | $120.00 |

Plus a $0.00005 start fee per run. The Apify free plan's $5 monthly credit covers about 4,150 websites. The prefilled input (`apify.com` and `hetzner.com`, up to 6 pages each) returns 2 rows in about 15 seconds and costs about $0.0025.

#### Why this one

- **The lowest per-website price in the top results**: $1.20 per 1,000 sites, against $1.25-$20.00 for the other pay-per-website scrapers and about $12 for per-page billing.
- **One flat price per website**: up to 30 pages per site (`maxPagesPerWebsite`) for the same $0.0012, so deeper crawls do not multiply the bill.
- **Failed websites are free**: dead domains and strictly protected sites get a `status: "failed"` row with the error and cost nothing.
- **Emails ranked, not just dumped**: same-domain role inboxes (info@, sales@) first, with `emailSources` showing where each was found; Cloudflare-protected and `[at]` / `[dot]` addresses are decoded.
- **Phones validated** with libphonenumber, in international and E.164 format.
- **Company social profiles only**: site-wide LinkedIn, Facebook, Instagram, X, YouTube, TikTok and 8 more networks; share buttons and personal profiles are skipped.

### FAQ

**Is it legal to scrape contact details from websites?**
The actor only reads public pages and the contact details a business publishes about itself (general inboxes, switchboard numbers, company social pages). It skips personal LinkedIn profiles and does not follow team or people pages. Emails can still be personal data under GDPR and similar laws, so use the results for legitimate business purposes and follow anti-spam rules. This is not legal advice.

**Does it need a login, API key or special proxy?**
No login and no API key. The default Apify Proxy (datacenter) works for the vast majority of company websites. About 5% of sites sit behind strict bot protection (Cloudflare, Akamai); they come back as `status: "failed"` and cost nothing.

**How fast is it and how many websites can I send?**
It reads plain HTML with no browser and 20 parallel requests by default, so 1,000 websites finish in minutes. You can paste tens of thousands of domains; each becomes exactly one row, deduplicated by domain.

**Why does a website have no email?**
Many sites only offer a contact form. The actor reports what the site publishes; it never guesses addresses. Single-page apps that render everything in the browser may also return fewer contacts.

**Can I schedule it or use it via API?**
Yes: Apify Schedules, the REST API and JavaScript / Python clients, Make, Zapier, n8n, webhooks, or the Apify MCP server for AI agents.

**Can I crawl more pages per site?**
Yes, up to 30 with `maxPagesPerWebsite`. The price stays per website.

### How to use

1. Paste your **Websites or domains**, one per line: `stripe.com`, `www.thomann.de` or `https://www.joesstonecrab.com`. Lists exported from Google Maps, a CRM, a directory or a spreadsheet work as they are.
2. Optionally change **Max pages per website** (default 6; 1 = homepage only), set a **Default phone country** for numbers written without a prefix, or tick **Only emails on the website's own domain**.
3. Click **Start**. Download the results as CSV, Excel, JSON or HTML, or send them to your CRM, Google Sheets, Make, Zapier, n8n or an AI agent through the Apify API / MCP.

### Input example

```json
{
    "websites": ["apify.com", "hetzner.com", "https://www.joesstonecrab.com", "thomann.de"],
    "maxPagesPerWebsite": 6,
    "scanTextForPhones": true,
    "onlySameDomainEmails": false
}
```

### Output example

Real row from a cloud run:

```json
{
    "website": "hetzner.com",
    "domain": "hetzner.com",
    "finalUrl": "https://www.hetzner.com/",
    "status": "ok",
    "companyName": "Hetzner",
    "title": "Dedicated Servers, Cloud & Hosting from Germany | Hetzner",
    "language": "en-US",
    "email": "info@hetzner.com",
    "emails": ["info@hetzner.com", "dsa-authorities@hetzner.com", "tco@hetzner.com"],
    "emailSources": {
        "info@hetzner.com": "mailto @ /legal/legal-notice/",
        "dsa-authorities@hetzner.com": "mailto @ /legal/legal-notice/",
        "tco@hetzner.com": "mailto @ /legal/legal-notice/"
    },
    "phone": "+49 9831 5050",
    "phones": ["+49 9831 5050", "+49 9831 5053"],
    "phonesE164": ["+4998315050", "+4998315053"],
    "linkedin": "https://www.linkedin.com/company/hetzner-online",
    "facebook": "https://www.facebook.com/hetzner.de",
    "instagram": "https://www.instagram.com/hetzner.online",
    "twitter": "https://x.com/Hetzner_Online",
    "youtube": "https://www.youtube.com/user/HetznerOnline",
    "tiktok": null,
    "contactPageUrl": "https://www.hetzner.com/legal/legal-notice/",
    "pagesCrawled": 6,
    "scrapedAt": "2026-10-09T17:27:53.266Z"
}
```

On a 67-website test list (SaaS, e-commerce, restaurants, German retailers) 63 loaded; of those 60% had an email, 33% a phone, 87% at least one social profile, 83% a contact page and 25% a full postal address.

### API example

Python (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("rel8ble/website-contact-extractor").call(run_input={"websites":["kinsta.com","teamviewer.com","sennheiser.com"],"maxPagesPerWebsite":6})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["domain"], item.get("email"), item.get("phone"), item.get("linkedin"))
```

One HTTP call that runs the actor and returns the rows (curl):

```bash
curl -X POST "https://api.apify.com/v2/acts/rel8ble~website-contact-extractor/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"websites":["kinsta.com","teamviewer.com","sennheiser.com"],"maxPagesPerWebsite":6}'
```

### Integrations

- **API**: start runs and read datasets over the Apify REST API or the JavaScript / Python clients.
- **MCP / AI agents**: available through the Apify MCP server; every input and output field is documented.
- **No-code**: Make, Zapier, n8n, Google Sheets, HubSpot, Airtable, Slack, webhooks.
- **Chaining**: feed it the `website` column of a Google Maps, directory or YC companies scraper.

### How it works (for developers)

For each domain the homepage is fetched (falling back to `www.` / `http://` variants), then same-site links are scored by contact likelihood in nine languages (contact, impressum, mentions-legales, about, support, locations...) and the best ones are fetched up to the page limit; if the homepage links are injected by JavaScript, `/contact`, `/contact-us`, `/about` and `/impressum` are probed. Every page is parsed with Cheerio for `mailto:` and `tel:` links, Cloudflare `data-cfemail` blocks, schema.org Organization / LocalBusiness JSON-LD (email, telephone, address, sameAs, logo) and social links; visible text is scanned for plain and obfuscated emails and, on contact-type pages, for phone numbers that libphonenumber validates as full-length numbers. Results are merged per domain and pushed once.

```bash
npm install
npm test             # extraction tests on synthetic and saved real pages
node src/main.js     # input in storage/key_value_stores/default/INPUT.json
```

# Actor input Schema

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

Required. Company websites, one per line: bare domains ("stripe.com"), hosts ("www.thomann.de") or full URLs ("https://www.joesstonecrab.com"). Each website becomes exactly one result row, deduplicated by domain. Paste up to tens of thousands; a list from Google Maps, a CRM export or a directory works well.

## `maxPagesPerWebsite` (type: `integer`):

Optional. Pages read per website, integer 1-30, including the homepage. Default 6. The actor opens the homepage, then the most contact-like links first (contact, imprint/impressum, about, support, locations). 1 = homepage only (fastest); 10+ = deeper for large sites. The price is per website, so more pages do not cost more.

## `defaultCountry` (type: `string`):

Optional. Two-letter country code used to read phone numbers written without a country prefix, e.g. "US", "GB", "DE". Empty (default) = guessed from the domain ending (.de = DE, .co.uk = GB), US for .com/.net/.io. Numbers are returned in international format either way.

## `scanTextForPhones` (type: `boolean`):

Optional boolean. true (default) = also read phone numbers written in the text of contact, imprint and about pages (validated, full-length numbers only). false = only clickable tel: links and structured data, for the cleanest possible list.

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

Optional boolean. true = keep only emails on the website's own domain (info@acme.com for acme.com) and drop agency, Gmail or partner addresses. Default false.

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

Optional, advanced. Parallel requests, integer 1-50. Default 20. Each website is a different server, so high values are safe.

## `maxRequestRetries` (type: `integer`):

Optional, advanced. Retries per page on timeouts and errors, integer 0-6. Default 2. Websites that do not resolve are not retried.

## `proxyConfiguration` (type: `object`):

Optional. Apify Proxy (datacenter) is the default and works for the vast majority of company websites. Sites behind strict bot protection (about 5%) come back with status failed and are not charged.

## Actor input object example

```json
{
  "websites": [
    "apify.com",
    "hetzner.com"
  ],
  "maxPagesPerWebsite": 6,
  "defaultCountry": "",
  "scanTextForPhones": true,
  "onlySameDomainEmails": false,
  "maxConcurrency": 20,
  "maxRequestRetries": 2,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One row per website: emails, phones, social profiles, address, company name and contact page.

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

Websites processed, how many had an email / phone / social profile, failed websites and pages crawled.

# 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",
        "hetzner.com"
    ],
    "maxPagesPerWebsite": 6,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("rel8ble/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 = {
    "websites": [
        "apify.com",
        "hetzner.com",
    ],
    "maxPagesPerWebsite": 6,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("rel8ble/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 '{
  "websites": [
    "apify.com",
    "hetzner.com"
  ],
  "maxPagesPerWebsite": 6,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call rel8ble/website-contact-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,rel8ble/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/hGKmcNVLlsczDc2vr/builds/xcNGQBYnkqya9ph1K/openapi.json
