# Contact Details Scraper: Emails, Phones & Socials (`zaiq/contact-details-extractor`) Actor

Email extractor and contact details scraper for leads: give company websites, get emails, phone numbers (E.164) and social media links (LinkedIn, X, Facebook, Instagram, YouTube, TikTok) from contact, about and legal pages, with source pages. Pay only for websites with contacts.

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

## Pricing

from $16.00 / 1,000 websites

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

## Contact Details Scraper: Emails, Phones & Socials

Give it a list of company websites and get their contact details back: email addresses, phone numbers, social media
links (LinkedIn, X/Twitter, Facebook, Instagram, YouTube, TikTok and more), contact pages and the company's own
schema.org data. One row per website, deduplicated, with the page each detail was found on. A contact details scraper
and email extractor for lead lists, priced per website, and only for websites where an email or phone number was found.

### What it does

For each website it reads the home page and then the pages most likely to list contacts: Contact, Legal notice
(Impressum, Imprint, Mentions légales, Aviso legal), About, Team, Support, Locations, Terms, Privacy and Careers. It
finds these pages from link text and addresses in about 25 languages, reads the best page of each kind first, and
stops at 8 pages per website by default.

- **Emails** from `mailto:` links, page text, Cloudflare-protected addresses, common disguises (`name [at] company [dot] com`, `name(at)company.de`, an `@` hidden in a separate element) and schema.org. Image names such as
  `logo@2x.png`, error-tracker keys, placeholder and example addresses, and addresses inside elements marked hidden
  (a common spam trap) are dropped. Each address says whether it is on the website's own domain and whether it is a generic mailbox
  (info@, sales@, kontakt@) or looks like one person's.
- **Phone numbers** from `tel:` links, WhatsApp links, schema.org and page text, checked with Google's phone number
  library and returned in E.164 (`+442079460958`), local and international format, with country and type (fixed line,
  mobile, toll-free ...). Numbers in text count only when they are labelled (Tel, Phone, Telefon, 電話, 대표번호 ...),
  written with a country code, or toll-free, so dates, VAT and registration numbers are not mistaken for phone
  numbers. Faxes are flagged.
- **The phone country** comes from the website itself: its domain ending, its schema.org address, its own `+xx`
  numbers, its language and the countries it names. A number listed under a country's name in an office list
  ("Mexico 800 681 6929") is read for that country. You can also set one country for all websites.
- **Social profiles**: LinkedIn company pages (and staff profiles, kept apart), X/Twitter, Facebook, Instagram,
  YouTube, TikTok, GitHub, Pinterest, WhatsApp and Telegram, normalised and deduplicated. Share buttons, single posts,
  videos and profiles that only appear in embedded customer posts are left out.
- **Contact pages and contact forms**, the **company name**, and the **schema.org Organization / LocalBusiness**
  record (legal name, address, phones, emails, `sameAs` profiles, contact points).
- **JavaScript when needed**: a page is loaded in a real browser only when its HTML is built by scripts, its menu is
  drawn by a script, or its addresses are written in by a script, and once more as a last try when nothing was found.
  At most two pages per website, at no extra cost.

### Who uses it

- **Lead generation and sales prospecting**: turn a list of company domains (from a CRM export, a directory or a
  Google Maps search) into emails, phone numbers and LinkedIn pages.
- **CRM enrichment and data cleaning**: fill missing contact fields, check which websites still publish a contact.
- **Partnerships, outreach and PR**: find the right mailbox (sales@, press@, partners@) rather than a generic form.
- **Market research**: build lists of companies with their legal names, addresses and social presence.

### Example

A test run on 4 October 2026 read 70 public websites of software, hosting and online-service companies in 22
countries, ten at a time, with the default settings:

| Result | Websites |
|---|---|
| At least one email or phone number (charged) | 61 of 70 |
| Email address found | 56 |
| Phone number found | 53 |
| Social media profiles found | 63 (LinkedIn company page: 56) |
| schema.org organisation data | 52 |
| No email or phone on the pages read (free) | 5 |
| Bot wall on every try (free) | 4 |

Of the 66 websites that did not block the run, 61 (92%) returned an email or phone number. The run found 231 email
addresses and 206 phone numbers; each was checked again against the page it was credited to, and all 437 were there.
In a manual review, 227 of the 231 addresses were the company's own (or its group's or legal representative's); the
other four came from a product demo picture and an AI assistant's address. A typical website took 5 seconds and 7
pages, with about 0.4 MB of traffic; 15 of the 70 needed a browser for at least one page. The 70 websites took just
over a minute in all.

### Input

```json
{
  "startUrls": [{ "url": "https://www.example.com" }],
  "domains": ["example.org", "example.co.uk"],
  "maxPagesPerSite": 8,
  "renderMode": "auto",
  "includeSocials": true,
  "proxyConfiguration": { "useApifyProxy": true }
}
```

- `startUrls`: website URLs. Paste a list, upload a text or CSV file, or link a Google Sheet.
- `domains`: bare domains, one per line (`example.org`). Use either field, or both.
- A deep address (`https://example.com/contact`) is read first, then the home page. Duplicates of the same website are
  read once; inputs that are not websites (an email address, `localhost`, a private IP) get a free row saying why.
- `maxPagesPerSite`: pages per website including the home page (1 to 30, default 8). The price per website does not
  change.
- `pageTypes`: which kinds of linked pages to read (contact, legal notice, about, team, support, locations, terms,
  privacy, careers). All by default.
- `renderMode`: `auto` (default) uses a browser only where needed; `never` reads plain HTML only; `always` loads every
  page in a browser, which is slower and charged at the browser price.
- `defaultCountry`: a two-letter country code for numbers written without a country code. Leave it empty to work it out
  per website.
- `includePeopleProfiles` (off by default): also return staff LinkedIn profiles linked from team pages. These are
  personal data; turn it on only with a lawful basis.
- `onlyGenericEmails`: keep only role mailboxes (info@, sales@, support@). `sameDomainEmailsOnly`: drop addresses at
  other domains. `keepNoReplyEmails`: keep noreply@ addresses (dropped by default).
- `proxyConfiguration`: Apify's automatic (datacenter) proxy by default. Choose residential proxies for websites that
  block datacenter addresses (charged at the residential price).
- `respectRobotsTxt` (default on), `maxConcurrency` (websites at once, default 10), `maxTriesPerPage` (default 3; a
  blocked or failed page is tried again from a new proxy address, free).

### Output

One row per website. This row is from the test run, with the company's name, domain, numbers and profiles replaced
and some list entries removed; the structure and the other values are as returned.

```json
{
  "domain": "example-hosting.co.uk",
  "url": "https://example-hosting.co.uk/",
  "status": "found",
  "companyName": "Example Hosting Ltd",
  "primaryEmail": "sales@example-hosting.co.uk",
  "primaryPhone": "+442079460958",
  "emails": [
    { "email": "sales@example-hosting.co.uk", "sameDomain": true, "generic": true, "noReply": false,
      "source": "mailto", "sourceUrl": "https://www.example-hosting.co.uk/" },
    { "email": "help@example-hosting.co.uk", "sameDomain": true, "generic": true, "noReply": false,
      "source": "mailto", "sourceUrl": "https://www.example-hosting.co.uk/" },
    { "email": "billing@example-hosting.co.uk", "sameDomain": true, "generic": true, "noReply": false,
      "source": "mailto", "sourceUrl": "https://www.example-hosting.co.uk/" }
  ],
  "phones": [
    { "e164": "+442079460958", "national": "020 7946 0958", "international": "+44 20 7946 0958", "country": "GB",
      "type": "fixed_line", "extension": null, "fax": false, "source": "tel-link",
      "sourceUrl": "https://www.example-hosting.co.uk/" },
    { "e164": "+448081570123", "national": "0808 157 0123", "international": "+44 808 157 0123", "country": "GB",
      "type": "toll_free", "extension": null, "fax": false, "source": "tel-link",
      "sourceUrl": "https://www.example-hosting.co.uk/" },
    { "e164": "+442079460959", "national": "020 7946 0959", "international": "+44 20 7946 0959", "country": "GB",
      "type": "fixed_line", "extension": null, "fax": true, "source": "text",
      "sourceUrl": "https://www.example-hosting.co.uk/contact/" }
  ],
  "socials": {
    "linkedin": ["https://www.linkedin.com/company/example-hosting"],
    "linkedinPeople": [],
    "x": ["https://x.com/examplehosting"],
    "facebook": ["https://www.facebook.com/examplehosting"],
    "instagram": ["https://www.instagram.com/examplehosting"],
    "youtube": ["https://www.youtube.com/user/examplehosting"],
    "tiktok": [], "github": [],
    "pinterest": ["https://www.pinterest.com/examplehosting"],
    "whatsapp": [], "telegram": []
  },
  "contactPages": ["https://www.example-hosting.co.uk/contact/"],
  "contactFormPages": ["https://www.example-hosting.co.uk/contact/"],
  "schemaOrg": {
    "type": "Organization",
    "name": "Example Hosting Ltd",
    "url": "https://www.example-hosting.co.uk/",
    "sameAs": ["https://www.facebook.com/examplehosting", "https://x.com/examplehosting"]
  },
  "finalUrl": "https://www.example-hosting.co.uk/",
  "phoneRegion": "GB",
  "phoneRegionSource": "international numbers on the site",
  "pagesVisited": [
    { "url": "https://www.example-hosting.co.uk/", "kind": "home", "status": 200, "rendered": false },
    { "url": "https://www.example-hosting.co.uk/contact/", "kind": "contact", "status": 200, "rendered": false }
  ],
  "errors": [],
  "error": null,
  "input": "example-hosting.co.uk",
  "crawl": { "pages": 8, "renderedPages": 0, "seconds": 3.5, "kilobytes": 160 },
  "crawledAt": "2026-10-04T14:43:48Z",
  "charged": true
}
```

- `status`: `found` (at least one email or phone, charged), `no-email-or-phone`, `blocked`, `unreachable`,
  `robots-disallowed`, `invalid-input` or `error`. Only `found` is charged; `charged` says whether the row was.
- `primaryEmail` and `primaryPhone`: the best single contact for a CSV column (own domain and info@/contact@/sales@
  first; faxes never).
- `error` explains rows without contacts; `errors` lists pages that failed.
- The **Emails** and **Phones** views of the dataset (and the output tab) give one row per address or number, ready
  for a spreadsheet.

### Use it from code, automations and AI agents

- **API**: run it and get the results in one call: `POST https://api.apify.com/v2/acts/zaiq~contact-details-extractor/run-sync-get-dataset-items?token=YOUR_TOKEN` with the input as JSON. The API tab on this page has ready-made Python, JavaScript and cURL examples.
- **Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
run = client.actor("zaiq/contact-details-extractor").call(run_input={'startUrls': [{'url': 'https://www.hetzner.com'}, {'url': 'https://www.blacknight.com'}]})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

- **No code**: connect it to Google Sheets, Zapier, Make or n8n from the Integrations tab, schedule runs in Apify Console, and get a webhook when a run finishes.
- **AI agents (MCP)**: add it as a tool to Claude, Cursor or any MCP client through Apify's MCP server: `https://mcp.apify.com?tools=zaiq/contact-details-extractor`.

### Pricing

Pay per event: you pay only for websites where at least one email address or phone number was found. Apify's
platform usage is included.

| Event | Price |
|---|---|
| Website with contacts | $0.02 ($20 per 1,000 websites) |
| Website with contacts, read through residential proxies | $0.04 |
| Website with contacts, every page loaded in a browser (`renderMode: always`) | $0.05 |

Plus Apify's standard run start fee. The price is per website however many pages are read. Free: websites with no
email or phone number on the pages read (even if social profiles were found), unreachable websites, websites that
block every try, pages not allowed by robots.txt, invalid inputs and every retry. Your spending limit for the run is
respected: the run stops cleanly when it is reached, and websites still in progress are not charged.

### Limits

- It reads the home page and up to 30 linked pages per website (8 by default), not the whole website. Addresses that
  appear only deep in a blog or a help centre may be missed.
- Some websites show a bot wall (a Cloudflare challenge, for example) to automated visitors. Those rows come back as
  `blocked` and cost nothing; residential proxies get through some of them. The Actor does not solve CAPTCHAs.
- Addresses shown only as images, only after a click, or only through a contact form cannot be read. Numbers written
  in running text without a label, a country code or a toll-free prefix are skipped on purpose, to keep dates and
  registration numbers out.
- In automatic mode a page is rendered only when it looks built by scripts or nothing was found. A number that a
  script draws in on a page whose HTML already lists an email address can be missed; `renderMode: always` reads it.
- Emails are not verified by sending mail; they are the addresses the website publishes.
- At most 100 emails, 50 phone numbers and 20 profiles per network are kept per website.
- **Privacy and lawful use**: the Actor reads public company pages and is built for business contact details. Some
  results can still be personal data, for example an employee's name-based address or LinkedIn profile, and data
  protection laws such as the GDPR (EU and UK), POPIA (South Africa), CCPA (California) and anti-spam rules such as
  CAN-SPAM and PECR apply to how you store and use them. You are responsible for having a lawful basis and for honouring
  opt-outs. `onlyGenericEmails` keeps role mailboxes only, and staff LinkedIn profiles are off unless you turn them on. robots.txt is respected by default. Use it only on websites
  you may access, and follow their terms.

### FAQ

**Why is a website free with no contacts?** The `error` field says why: no email or phone on the pages read, a bot
wall, a domain that does not resolve, or robots.txt. Rows without contacts are never charged.

**How do I get one row per email for my spreadsheet?** Open the **Emails** view in the dataset (or the "Emails, one
per row" link in the output tab) and export it as CSV or Excel. The **Phones** view does the same for numbers.

**What does `sameDomain` mean?** The address is at the website's own domain, or at the same name under another
ending (acme.com for acme.de). Addresses at agencies, Gmail and other domains are kept but marked `false` and listed
last; `sameDomainEmailsOnly` removes them.

**Does it find personal emails?** It returns the addresses a company publishes on its own pages. Addresses on legal
and privacy pages that belong to other organisations (regulators, processors) are left out. Use `onlyGenericEmails`
to keep role mailboxes only.

**How are phone numbers formatted?** Every number is validated and returned in E.164 (`+442079460958` style), plus its
local and international formats, country and type. Extensions are kept separately.

**Which proxy should I use?** The default automatic proxy works for most company websites. Switch to residential
proxies (optionally with a country) if many rows come back as `blocked`.

**Can I give it a list from a file?** Yes: upload a text or CSV file, or link a Google Sheet or any text file in the
Websites field. Every cell that looks like a website or a domain is used.

# Actor input Schema

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

Company website URLs (https://www.example.com). Paste a list, upload a text or CSV file, or link a Google Sheet. For bare domains (example.com), use the Domains field below. Duplicates of the same site are read once.

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

Bare domains, one per line (example.com, www.example.co.uk). Use this or Websites above, or both.

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

The home page plus the most likely contact pages, up to this many. More pages can find more addresses but take longer. The price per website is the same.

## `pageTypes` (type: `array`):

Which linked pages to read after the home page, found by link text and address in about 25 languages (Contact, Kontakt, Contacto, お問い合わせ ...). The best page of each kind is read first.

## `renderMode` (type: `string`):

Automatic loads a page in a real browser only when its HTML is built by JavaScript, its menu is drawn by a script, or its email addresses are written in by a script, and once more as a last try for a website where nothing was found. At most two pages per website are rendered this way, at no extra cost.

## `includeSocials` (type: `boolean`):

Return LinkedIn, X (Twitter), Facebook, Instagram, YouTube, TikTok, GitHub, Pinterest, WhatsApp and Telegram profiles linked from the website. Share buttons and links to single posts are left out.

## `includePeopleProfiles` (type: `boolean`):

Off by default. Staff profiles linked from team pages are personal data (GDPR, POPIA, CCPA): turn this on only if you have a lawful basis to process them.

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

Two-letter country code used to read phone numbers written without a country code (for example 020 7946 0958). Leave empty to work it out for each website from its domain ending, its address, its language and its own numbers.

## `onlyGenericEmails` (type: `boolean`):

Keep only role mailboxes such as info@, sales@, support@ and kontakt@, and leave out addresses that look like one person's (jane.smith@). Useful when you must avoid personal data.

## `sameDomainEmailsOnly` (type: `boolean`):

Leave out addresses at other domains, such as a web agency's address in the footer or a Gmail address. Sister domains of the same name (acme.com for acme.de) count as the website's own.

## `keepNoReplyEmails` (type: `boolean`):

Keep addresses like noreply@ and donotreply@ (flagged noReply). They are left out by default because they do not reach anyone.

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

Apify's automatic (datacenter) proxy is the default and works for most company websites. Choose residential proxies (and a country if you like) for websites that block datacenter addresses; websites read through residential proxies are charged at the residential price.

## `respectRobotsTxt` (type: `boolean`):

Skip pages that the website's robots.txt does not allow. A website whose home page is not allowed comes back as robots-disallowed, free.

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

How many websites are read at the same time. Each website's pages are read a few at a time.

## `maxTriesPerPage` (type: `integer`):

When a page is blocked (403, 429, a bot wall) or the connection fails, it is tried again from a new proxy address, up to this many times. Retries are free.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.hetzner.com"
    },
    {
      "url": "https://www.blacknight.com"
    },
    {
      "url": "https://apify.com"
    }
  ],
  "maxPagesPerSite": 8,
  "pageTypes": [
    "contact",
    "impressum",
    "about",
    "team",
    "support",
    "locations",
    "legal",
    "privacy",
    "careers"
  ],
  "renderMode": "auto",
  "includeSocials": true,
  "includePeopleProfiles": false,
  "onlyGenericEmails": false,
  "sameDomainEmailsOnly": false,
  "keepNoReplyEmails": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "respectRobotsTxt": true,
  "maxConcurrency": 10,
  "maxTriesPerPage": 3
}
```

# Actor output Schema

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

Dataset with one row per website.

## `emails` (type: `string`):

The same dataset with one row per email address, for lead lists.

## `phones` (type: `string`):

The same dataset with one row per phone number.

# 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 = {
    "startUrls": [
        {
            "url": "https://www.hetzner.com"
        },
        {
            "url": "https://www.blacknight.com"
        },
        {
            "url": "https://apify.com"
        }
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("zaiq/contact-details-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 = {
    "startUrls": [
        { "url": "https://www.hetzner.com" },
        { "url": "https://www.blacknight.com" },
        { "url": "https://apify.com" },
    ],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("zaiq/contact-details-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 '{
  "startUrls": [
    {
      "url": "https://www.hetzner.com"
    },
    {
      "url": "https://www.blacknight.com"
    },
    {
      "url": "https://apify.com"
    }
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call zaiq/contact-details-extractor --silent --output-dataset

```

## MCP server setup

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