# Contact Details Scraper - Emails, Phones and Social Links (`sweet_stickball/contact-details-scraper`) Actor

Contact details scraper and email extractor for any list of websites: emails, phones in E.164, LinkedIn, X, Facebook, Instagram, YouTube, TikTok and GitHub profiles, address and company name from contact, Impressum and legal pages. Lead generation, no login.

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

## Pricing

from $3.00 / 1,000 website with contact details

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 and Social Links

Extract the public contact details of companies from a list of websites: email addresses, phone numbers, social profiles (LinkedIn, X/Twitter, Facebook, Instagram, YouTube, TikTok, GitHub, Pinterest), postal address and company name. One flat record per website.

No browser, no login, no API key, no start fee.

### What it does

For each website in `startUrls`:

1. Checks the site's `robots.txt` (on by default) and reads the home page.
2. Finds the contact-type pages of the same domain from the home page links, using multilingual keywords: contact, kontakt, contacto, contatti, impressum, imprint, mentions legales, legal notice, aviso legal, about, a propos, uber uns, quienes somos, team, equipe... If the home page links to none, the site's sitemap is used.
3. Reads up to `maxPagesPerSite` pages (home page included), contact pages first, then legal notice / Impressum, about and team pages.
4. Extracts:
   - **Emails** from `mailto:` links, the page text (including simple obfuscation such as `info [at] acme [dot] com` or `info(at)acme.de`), Cloudflare-protected addresses, schema.org data, and addresses of the site's own domain found in the page source.
   - **Phones** from `tel:` links, schema.org data and the page text, normalised to E.164 (`+4998315050`) when the country can be deduced (schema.org address, country domain such as `.fr` / `.de` / `.co.uk`, or the page language).
   - **Social profiles**: the profile or company page most linked from the site, per network. Share buttons, posts, videos and repositories are ignored.
   - **Address and company name** from schema.org `Organization` / `LocalBusiness` JSON-LD; company name falls back to `og:site_name`, then to the home page title.
5. Pushes one record per website, as soon as that website is done.

False positives are filtered: image file names that look like emails (`logo@2x.png`), placeholder and example addresses (`name@example.com`, `your@email.com`), error-tracking addresses (Sentry, Wix), dates, order numbers, prices, fax numbers and template numbers (`+44 7123 123456`).

Websites are read with plain HTTP first. A website that answers with a simple anti-bot page or refuses the connection is retried with a browser TLS fingerprint through a datacenter proxy, then through a residential proxy. No captcha is solved and no login is used.

### Input

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `startUrls` | array | Yes | - | Websites to scan: home page URLs or bare domains (`acme.com`). One record per domain, duplicates ignored. |
| `maxItems` | integer | No | 20 | Maximum number of websites to process (1-5000). |
| `maxPagesPerSite` | integer | No | 5 | Pages read per website, home page included (1-30). |
| `sameDomainOnly` | boolean | No | true | Only read pages of the website's domain and subdomains. |
| `respectRobotsTxt` | boolean | No | true | Skip pages disallowed by the website's robots.txt. |

Example:

```json
{
    "startUrls": [
        {"url": "https://www.hetzner.com"},
        {"url": "https://wikimediafoundation.org"},
        {"url": "paul.fr"}
    ],
    "maxItems": 3,
    "maxPagesPerSite": 5
}
```

### Output

One dataset item per website (real output, October 2026):

```json
{
    "domain": "hetzner.com",
    "url": "https://www.hetzner.com",
    "inputUrl": "https://www.hetzner.com",
    "companyName": "Hetzner",
    "emails": ["product-feedback@hetzner.com", "info@hetzner.com", "tco@hetzner.com", "dsa-authorities@hetzner.com", "contact-fi@hetzner.com"],
    "phones": ["+4998315050", "+358753259100", "+49911234226100", "+49374574447100", "+3587532590"],
    "primaryEmail": "info@hetzner.com",
    "primaryPhone": "+4998315050",
    "linkedin": "https://www.linkedin.com/company/hetzner-online",
    "twitter": "https://twitter.com/Hetzner_Online",
    "facebook": "https://www.facebook.com/hetzner.de",
    "instagram": "https://www.instagram.com/hetzner.online",
    "youtube": "https://www.youtube.com/channel/UC5GXP-_6UWl5I9pisIDohUA",
    "tiktok": null,
    "github": null,
    "pinterest": null,
    "address": null,
    "pagesCrawled": 5,
    "sourcePages": ["https://www.hetzner.com", "https://www.hetzner.com/support/", "https://www.hetzner.com/legal/legal-notice/", "https://www.hetzner.com/unternehmen/downloads/", "https://www.hetzner.com/unternehmen/ueber-uns/"],
    "error": null,
    "scrapedAt": "2026-10-28T21:42:42.384890+00:00"
}
```

| Field | Description |
|-------|-------------|
| `domain` | Website domain without `www.` |
| `url` / `inputUrl` | Home page after redirects / URL as given |
| `companyName` | schema.org name, else `og:site_name`, else home page title |
| `emails` | All distinct addresses found, lower case, `mailto:` links first |
| `phones` | Distinct numbers, E.164 when the country is known, else the digits as written |
| `primaryEmail` | Address on the site's own domain, generic role (`contact@`, `info@`, `hello@`...) preferred |
| `primaryPhone` | First number found (`tel:` links first) |
| `linkedin` ... `pinterest` | One profile URL per network, or `null` |
| `address` | Postal address from schema.org data, or `null` |
| `pagesCrawled` | Pages read on the website (0 when it could not be read) |
| `sourcePages` | Pages where at least one email, phone or social profile was found |
| `error` | Why the website could not be read, else `null` |

A website without any contact detail still returns a record, with empty lists and `null` values: it is **not charged**. A website that cannot be read (DNS error, HTTP error, anti-bot page, home page disallowed by robots.txt) returns a record with `pagesCrawled: 0` and the reason in `error`: it is not charged either. The run fails only if none of the websites could be read.

### Pricing

Pay-per-event, no start fee: **$5.00 per 1,000 websites with contact details** on the Apify Free plan, lower on paid plans. One `WEBSITE` event is charged per website for which at least one email, phone or social profile was found; websites with no result or that could not be read are free.

| Event | Free | Starter | Scale | Business | Enterprise |
|-------|------|---------|-------|----------|------------|
| Website with contact details (`WEBSITE`) | $5.00 | $4.30 | $3.70 | $3.00 | $3.00 |

Prices in USD per 1,000 events. The price per website does not depend on `maxPagesPerSite`.

### Limitations

- **JavaScript-only websites**: pages are read as HTML, without a browser. Contact details that are only rendered by JavaScript (single-page apps, contact widgets, some site builders) are not seen.
- **Emails shown as images**, contact forms without a written address, and strongly obfuscated addresses (JavaScript assembly, CSS tricks) are not extracted.
- **Protected websites**: sites behind a JavaScript challenge or captcha (for example some Cloudflare, DataDome or Akamai configurations) are returned with an `error` and not charged.
- **robots.txt**: with `respectRobotsTxt` on (default), websites that disallow all crawlers are skipped.
- **Address** is only filled when the website publishes schema.org `Organization` / `LocalBusiness` data (about a third of company sites in our tests).
- **Phones without a deducible country** (no country domain, no address data, no page language) are returned as written digits instead of E.164.
- Emails of other domains found on the pages (web agency credits, parent company, hosting provider) are kept in `emails`; `primaryEmail` prefers the site's own domain.
- Large sites with many local offices may list more contacts than `maxPagesPerSite` pages can cover.

### Data protection

The Actor only reads publicly available pages and returns contact details that companies publish about themselves (legal notice, contact page). Some pages also list named employees with their business email or phone. You are responsible for using the data in line with the GDPR and other applicable laws (legal basis, information of the persons, opt-out for marketing, no use of personal data beyond your purpose).

# Actor input Schema

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

Websites to scan, one per entry: a home page URL ('https://www.acme.com') or a bare domain ('acme.com'). Only one record is returned per domain; duplicates are ignored. Any page of a site can be given, the crawl starts from that page.

## `maxItems` (type: `integer`):

Maximum number of websites to process from the list (one output record per website). Extra entries are ignored.

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

Pages read per website, home page included. Contact pages are visited first, then legal notice / Impressum, about, and team pages. 5 is enough for most company sites; raise it for sites with many local offices.

## `sameDomainOnly` (type: `boolean`):

When enabled (default), only pages of the website's domain and its subdomains are read. Disable it to also follow contact links that point to another domain (for example a group site).

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

When enabled (default), pages disallowed by the website's robots.txt are not read. A website whose home page is disallowed is returned with an error message and is not charged.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.hetzner.com"
    },
    {
      "url": "https://wikimediafoundation.org"
    },
    {
      "url": "https://www.paul.fr"
    }
  ],
  "maxItems": 3,
  "maxPagesPerSite": 5,
  "sameDomainOnly": true,
  "respectRobotsTxt": true
}
```

# Actor output Schema

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

One record per website with emails, phones, social profiles and address, shown with the 'overview' view.

# 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://wikimediafoundation.org"
        },
        {
            "url": "https://www.paul.fr"
        }
    ],
    "maxItems": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("sweet_stickball/contact-details-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 = {
    "startUrls": [
        { "url": "https://www.hetzner.com" },
        { "url": "https://wikimediafoundation.org" },
        { "url": "https://www.paul.fr" },
    ],
    "maxItems": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("sweet_stickball/contact-details-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 '{
  "startUrls": [
    {
      "url": "https://www.hetzner.com"
    },
    {
      "url": "https://wikimediafoundation.org"
    },
    {
      "url": "https://www.paul.fr"
    }
  ],
  "maxItems": 3
}' |
apify call sweet_stickball/contact-details-scraper --silent --output-dataset

```

## MCP server setup

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