extracto.cloud
website-contact-extractor
Website Contact Extractor
Extract public contact information from website URLs, including email addresses, phone numbers and social links where available.
Current pricing: $0.001303 / 1 Website scanned · minimum 10 credits/run. Review the estimate before running.
Endpoint
Method
POST
URL
https://extracto.cloud/api/v1/parsers/website-contact-extractor
Result delivery
Add
webhook_url and optional webhook_secret to receive the saved result. Webhook setup and signatures →
Auth
X-API-KEY: ext_...Response
{
"success": true,
"tool": "website-contact-extractor",
"parser": "website-contact-extractor",
"count": 0,
"data": []
}
Documentation
# Website Email & Phone Contact Extractor (`valev-lab/website-contact-extractor`) parser
Extract public emails, valid phone numbers, social profiles, contact pages, forms, addresses, and VAT IDs from company websites. Get one CRM-ready row per domain with transparent scoring and source URLs. Use fast HTTP crawling by default, with optional JavaScript fallback and email verification.
- **URL**: https://extracto.cloud/docs/api/parsers/website-contact-extractor
- **Developed by:** [Daniel Valev](https://Extracto.com/valev-lab) (community)
- **Stats:** 9 total users, 5 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet
## Pricing
from $0.59 / 1,000 website scanneds
This parser is paid per event. You are not charged for the Extracto platform usage, but only a fixed price for specific events.
Since this parser supports Extracto Store discounts, the price gets lower the higher subscription plan you have.
Learn more: https://docs.Extracto.com/parsers/running/parsers-in-store.md#pay-per-event
## What's an Extracto parser?
An parser is a serverless cloud program that runs on the Extracto platform. It has two run modes.
In Batch mode, an parser 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 parser provides a web server which can be used as a website, API, or an MCP server.
Extracto vocabulary and the platform model are defined once, in the agent quickstart at https://Extracto.com/agents.md.
## How to integrate an parser?
If asked about integration, you help developers integrate parsers 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://Extracto.com/agents.md: the Extracto MCP server, Agent Skills with the Extracto 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 parser'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.Extracto.com/api/client/js/docs.md) (`npm install Extracto-client`) and [Python](https://docs.Extracto.com/api/client/python/docs.md) (`pip install Extracto-client`).
# README
**Website Email & Phone Contact Extractor** is a bulk **website contact scraper** for lead enrichment. Paste company domains to extract public emails, valid phone numbers, social profiles, contact and Impressum pages, forms, addresses, VAT IDs, and scored source records. Every deduplicated domain returns one CRM-ready row, including explicit failure, timeout, and budget statuses.
The default HTTP scan costs `$0.005 + $0.0008` per successfully scanned website, so one run can process up to 6,243 HTTP-only websites with the free $5 Extracto credit. JavaScript fallback and email verification are optional paid events.
### How to find emails and phone numbers from websites
1. Open the parser, click **Try for free**, and paste one website per line.
2. Keep the default HTTP-only scan, or enable paid email verification and JavaScript fallback when needed.
3. Click **Start** and export the resulting Dataset to JSON, CSV, Excel, XML, or your integration.
```json
{
"urls": [
"konditorei-buchwald.de"
],
"maxPagesPerSite": 10,
"verifyEmails": false,
"jsFallback": false,
"includePersonalNames": false
}
```
Inputs are case-insensitively deduplicated by registrable domain. `www.example.com` and `shop.example.com` therefore share one output row for `example.com`.
### What data can this website contact scraper extract?
| Data | What you receive |
| --- | --- |
| Emails | Primary and deduplicated public emails, 0–100 quality score, source URL, domain-match, role-account, free-provider, and disposable-domain flags |
| Phone numbers | Valid international numbers normalized to E.164, with original text and source page |
| Social profiles | LinkedIn company and people profiles, X, Facebook, Instagram, YouTube, GitHub, TikTok, and Pinterest |
| Contact routes | Contact, legal, support, team, and Impressum pages plus detected contact forms and form fields |
| Company data | Organization names, structured postal addresses, and explicitly published VAT or tax IDs |
| Run metadata | Status, pages crawled, duration, JavaScript fallback usage, timestamp, and actionable failure reason |
### Website contact scraper use cases
- Enrich company domains exported from Google Maps, LinkedIn, directories, or a CRM.
- Build a bulk business email and phone list while preserving one result row per input domain.
- Find German and multilingual Impressum or legal-page contacts.
- Detect contact-form-only companies instead of treating them as failed leads.
- Refresh contact data on a schedule and export it to JSON, CSV, Excel, Google Sheets, Make, Zapier, or n8n.
- Provide structured website contact data to AI agents through the Extracto API or MCP server.
### Website contact scraper output example
The default Dataset contains exactly one record per deduplicated input domain. The `overview` view shows the six fields an SDR usually needs first: `websiteUrl`, `primaryEmail`, `allEmails`, `primaryPhone`, `linkedin`, and `status`.
```json
{
"websiteUrl": "https://www.konditorei-buchwald.de/",
"primaryEmail": "[email protected]",
"allEmails": [
"[email protected]"
],
"primaryPhone": "+49303915931",
"linkedin": null,
"status": "success"
}
```
Email source precision is ordered as JSON-LD, `mailto:`, Cloudflare protection, plain text, and obfuscation. Scores are deterministic: source confidence, domain match, contact-page placement, repetition across pages, role-account status, free-provider status, and disposable-domain status all affect the 0–100 score.
### Website contact scraper input
| Parameter | Type | Default | What it controls |
| --- | --- | --- | --- |
| `urls` | string array | required | 1–10,000 bare domains or full URLs. Tracking parameters are removed and inputs are deduplicated by registrable domain. |
| `maxPagesPerSite` | integer | `20` | Hard cap of 1–200 attempted pages per website. It does not affect the event price. |
| `verifyEmails` | boolean | `false` | Verifies each extracted email. Charged per email that completes verification. |
| `maxConcurrency` | integer | `5` | Parallel page requests within one website; requests to the same host stay sequential. |
| `maxWebsitesConcurrency` | integer | `5` | Number of separate websites processed in parallel. |
| `requestTimeoutSecs` | integer | `15` | Per-request timeout from 5 to 60 seconds. |
| `maxSiteDurationSecs` | integer | `90` | Hard per-site wall-clock limit from 20 to 600 seconds. |
| `respectRobotsTxt` | boolean | `true` | Honors `robots.txt`. Disable only when you have a lawful reason and permission. |
| `jsFallback` | boolean | `false` | If HTTP finds no email, renders only the best contact page and homepage. Charged only after a browser pass completes. |
| `useProxy` | boolean | `false` | Uses the configured proxy. Proxy traffic can add charges from your proxy plan. |
| `proxyConfiguration` | object | Extracto Proxy | Proxy groups or custom proxy URLs, used only when `useProxy` is enabled. |
| `verificationLevel` | `format`, `mx`, or `smtp` | `mx` | Format is local; MX checks mail servers; SMTP probes a mailbox and a random catch-all address. |
| `smtpFromAddress` | string | none | Optional own-domain SMTP sender address. Used only for SMTP verification. |
| `includePersonalNames` | boolean | `true` | Associates mailto addresses with nearby names and job titles where possible. |
### How much does website contact scraping cost?
This parser uses pay per event. A normal HTTP page count does not change the website price.
| Event | When it is charged | Free-tier price |
| --- | --- | ---: |
| `parser-start` | Once when the run starts | $0.0050 |
| `website-scanned` | After a successful `success` or `no_contacts` record is stored | $0.0008 |
| `email-verified` | For each email that completes the selected verification level | $0.0015 |
| `js-fallback-used` | For each website whose opt-in browser fallback completes | $0.0030 |
Without verification or JavaScript fallback, the formula is `$0.005 + $0.0008 × successfully scanned websites`.
| Websites in one run | Worked cost |
| ---: | ---: |
| 1 | $0.0058 |
| 10 | $0.0130 |
| 100 | $0.0850 |
| 1,000 | $0.8050 |
For 100 websites with one verified email each, the cost is `$0.005 + $0.080 + $0.150 = $0.235`. If all 100 also need JavaScript fallback, add `$0.300`, for a total of `$0.535`.
With the free $5 Extracto credit, one HTTP-only run can scan up to 6,243 successful websites at Free-tier event prices. Actual capacity is lower when verification, JavaScript fallback, paid proxy traffic, or multiple run starts are used. Bronze, Silver, and Gold tiers discount every event by approximately 5%, 12%, and 26%; higher tiers receive further discounts.
Failed scans are stored as reconciliation rows but are not charged as `website-scanned`. When a maximum run cost or paid-item limit is reached, the remaining domains receive `skipped_budget` rows instead of disappearing.
### Why this website email scraper finds more contacts
- JSON-LD and schema.org microdata for `Organization`, `LocalBusiness`, `Person`, and `ContactPoint`
- Cloudflare `/cdn-cgi/l/email-protection` and `data-cfemail` decoding
- `name [at] domain [dot] com`, entity-encoded, and split inline addresses
- Multilingual contact, legal, team, and impressum pages
- Valid E.164 phone numbers with country inference
- LinkedIn, X, Facebook, Instagram, YouTube, GitHub, TikTok, and Pinterest profiles
- Share-link and social-intent exclusion
- Contact-form detection when no direct email is published
- Optional JavaScript rendering and format, MX, or SMTP verification
### Honest limitations
Expect roughly 20–30% of websites to publish no extractable email. Some companies intentionally offer only a contact form; those results can still return `contactForm`. Emails inserted only after complex interaction, login, consent, or CAPTCHA may remain unavailable even with JavaScript fallback. The parser does not bypass access controls.
`jsFallback` is opt-in because a browser is slower and has its own paid event. It runs only when the HTTP pass finds zero emails, and only for the homepage and best contact-page candidate.
Google Workspace, Microsoft 365 and Yahoo reject or greylist SMTP probing from cloud IPs. For those providers cap confidence at 45 and never set `isVerified: true`. Many cloud hosts also block outbound port 25 entirely — detect `ECONNREFUSED`/`ETIMEDOUT` on connect and return `smtpAvailable: false` rather than silently reporting everything as invalid. Overstating verification accuracy is the fastest way to earn 1-star reviews.
MX verification proves that a domain accepts email, not that an individual mailbox exists. Catch-all SMTP domains accept random recipients, so their individual addresses remain unverified.
### Integrations
Export directly to Google Sheets or connect the Dataset to Make, Zapier, n8n, and Extracto webhooks. Scheduled parser runs can refresh a CRM enrichment list on a daily, weekly, or monthly cadence. API and webhook consumers should use `domain` as the reconciliation key and inspect `status` before starting outreach.
### API
#### Node.js
```javascript
import { ApifyClient } from 'Extracto-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.parser('valev-lab/website-contact-extractor').call({
urls: ['Extracto.com', 'wordpress.org'],
verifyEmails: true,
verificationLevel: 'mx',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```
#### Python
```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.parser("valev-lab/website-contact-extractor").call(
run_input={
"urls": ["Extracto.com", "wordpress.org"],
"verifyEmails": True,
"verificationLevel": "mx",
}
)
items = client.dataset(run["defaultDatasetId"]).list_items().items
print(items)
```
#### cURL
```bash
curl --request POST \
"https://extracto.cloud/api/v1/parsers/website-contact-extractor" \
--header "Content-Type: application/json" \
--data '{
"urls": ["Extracto.com", "wordpress.org"],
"verifyEmails": false
}'
```
Read the Dataset with `GET https://api.Extracto.com/v2/datasets/{defaultDatasetId}/items?clean=true`.
### Use from MCP and AI agents
```bash
claude mcp add --transport http Extracto "https://mcp.Extracto.com?tools=valev-lab/website-contact-extractor"
```
Example prompts:
- “Enrich these 200 company domains with their best public email, phone number, and LinkedIn company page.”
- “Find the German Impressum contacts for this domain list and return only domain-matching emails with scores above 50.”
- “Check these prospect websites, verify their emails at MX level, and separate contact-form-only companies.”
### Legality and compliance
This parser extracts data that websites publish publicly and does not log in, bypass access controls, solve CAPTCHAs, or access private profiles. You are responsible for having a lawful purpose, respecting website terms and `robots.txt`, honoring GDPR and other privacy rights, following CAN-SPAM and similar outreach laws, maintaining suppression lists, and using the output responsibly. Public availability does not automatically grant permission for unsolicited marketing.
### FAQ
#### How do I scrape email addresses from a list of websites?
Paste the domains into `urls` and run the parser. It prioritizes structured data, mail links, Cloudflare-protected addresses, visible text, and common obfuscation, then returns one row per deduplicated domain.
#### How can I find business phone numbers from company websites?
The parser checks `tel:` links, JSON-LD telephone fields, and visible text. It validates candidates with `libphonenumber-js` and returns only valid E.164 numbers plus their original text.
#### Can I scrape emails from German Impressum pages?
Yes. German `Impressum` and `Kontakt` paths receive the highest crawl priority, alongside multilingual legal and contact-page terms.
#### Does this website email scraper work on JavaScript websites?
The default path uses fast HTTP requests. Enable `jsFallback` to render the best contact page and homepage when HTTP found zero emails.
#### Can this parser decode Cloudflare protected email addresses?
Yes. It decodes both Cloudflare email-protection links and `data-cfemail` attributes using Cloudflare's XOR format.
#### How accurate is SMTP email verification?
SMTP is a useful signal, not a guarantee. Catch-all servers, greylisting, cloud-IP blocks, and providers such as Google Workspace, Microsoft 365, and Yahoo limit mailbox-level certainty.
#### Why does a website return no email but a contact form?
Some businesses deliberately publish only a form. The parser reports a compatible form's URL, method, and fields so your workflow can distinguish “form only” from a failed scan.
#### Will the parser return a row when a website is down?
Yes. Invalid, failed, timed-out, and budget-skipped inputs still produce explicit rows for reconciliation; only duplicate registrable domains are combined.
#### Does increasing maxPagesPerSite increase the price?
No. Page count does not change the `website-scanned` event price, although larger crawls can run longer.
#### Can I send the extracted contacts to Google Sheets, Zapier, Make, or n8n?
Yes. Use Dataset exports, native Extracto integrations, webhooks, or the REST API to send results to those tools and your CRM.
### Changelog
#### 0.1.0 — 2026-08-24
- Initial production release.
- Added HTTP-first crawling, multilingual page scoring, JSON-LD and microdata, Cloudflare and obfuscation decoding, phone and social extraction, forms, optional Playwright fallback, three verification levels, migration-safe state, budget guards, complete schemas, and pay-per-event billing.
# Changelog
This parser's version history is a separate document: https://extracto.cloud/docs/api/parsers/website-contact-extractor/changelog.md
# parser input Schema
## `urls` (type: `array`):
Required. Add 1–10,000 bare domains or full website URLs, one per line. Inputs are normalized and deduplicated by company domain before scanning.
## `maxPagesPerSite` (type: `integer`):
Maximum number of pages to inspect on each website, from 1 to 200. This setting does not affect the price.
## `verifyEmails` (type: `boolean`):
Optionally check each found email at the selected verification level. This is charged once per email actually verified.
## `maxConcurrency` (type: `integer`):
Maximum number of page requests running at once within a website, from 1 to 20. Requests to any single host remain sequential.
## `maxWebsitesConcurrency` (type: `integer`):
Maximum number of different websites scanned at the same time, from 1 to 20.
## `requestTimeoutSecs` (type: `integer`):
How many seconds to wait for an individual page before treating that request as failed.
## `maxSiteDurationSecs` (type: `integer`):
Hard wall-clock limit for one website. This prevents a slow or broken site from delaying the rest of your lead list.
## `respectRobotsTxt` (type: `boolean`):
Honor each website's robots.txt rules. Enabled by default; turn it off only when you have a lawful reason and permission to do so.
## `jsFallback` (type: `boolean`):
When the HTTP scan finds no email, retry only the homepage and best contact page in a headless browser. Each website that uses this fallback triggers a separate charged event.
## `useProxy` (type: `boolean`):
Route requests through the selected proxy configuration. Proxy traffic may add charges from your Extracto proxy plan.
## `proxyConfiguration` (type: `object`):
Choose Extracto Proxy or your own proxy URLs. This setting is used only when “Use a proxy” is enabled and proxy traffic may add charges.
## `verificationLevel` (type: `string`):
Choose format-only validation, mail-server (MX) lookup, or an SMTP mailbox probe. Higher levels can take longer; every email actually checked is charged once.
## `smtpFromAddress` (type: `string`):
Optional. Use a sender address on a domain you own for SMTP probes; this can improve acceptance and accuracy. Used only with the SMTP verification level.
## `includePersonalNames` (type: `boolean`):
Try to associate each public email with a nearby person's name and job title when the page provides that context.
## parser input object example
```json
{
"urls": [
"konditorei-buchwald.de"
],
"maxPagesPerSite": 20,
"verifyEmails": false,
"maxConcurrency": 5,
"maxWebsitesConcurrency": 5,
"requestTimeoutSecs": 15,
"maxSiteDurationSecs": 90,
"respectRobotsTxt": true,
"jsFallback": false,
"useProxy": false,
"proxyConfiguration": {
"useApifyProxy": true
},
"verificationLevel": "mx",
"smtpFromAddress": "[email protected]",
"includePersonalNames": true
}
```
# parser output Schema
## `results` (type: `string`):
Complete contact records in the run's default dataset.
## `overview` (type: `string`):
The six-column overview for quick prospecting and CRM reconciliation.
# API
You can run this parser 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 'Extracto-client';
// Initialize the ApifyClient with your Extracto API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
token: '<YOUR_API_TOKEN>',
});
// Prepare parser input
const input = {
"urls": [
"konditorei-buchwald.de"
],
"proxyConfiguration": {
"useApifyProxy": true
}
};
// Run the parser and wait for it to finish
const run = await client.parser("valev-lab/website-contact-extractor").call(input);
// Fetch and print parser results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.Extracto.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.Extracto.com/api/client/js/docs
```
## Python example
```python
from apify_client import ApifyClient
# Initialize the ApifyClient with your Extracto API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")
# Prepare the parser input
run_input = {
"urls": ["konditorei-buchwald.de"],
"proxyConfiguration": { "useApifyProxy": True },
}
# Run the parser and wait for it to finish
run = client.parser("valev-lab/website-contact-extractor").call(run_input=run_input)
# Fetch and print parser results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.Extracto.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.Extracto.com/api/client/python/docs/quick-start
```
## CLI example
```bash
echo '{
"urls": [
"konditorei-buchwald.de"
],
"proxyConfiguration": {
"useApifyProxy": true
}
}' |
Extracto call valev-lab/website-contact-extractor --silent --output-dataset
```
## MCP server setup
```json
{
"mcpServers": {
"Extracto": {
"type": "http",
"url": "https://mcp.Extracto.com/?tools=fetch-parser-details,valev-lab/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 Extracto Console (https://console.Extracto.com/settings/integrations).
## OpenAPI specification
Download the OpenAPI definition: https://api.Extracto.com/v2/parsers/eV4DmI1DO3I3uk3J6/builds/VfCNN0HiznjXjwgfg/openapi.json
Request Body
{
"input": {
"urls": [
"konditorei-buchwald.de"
],
"maxPagesPerSite": 20,
"verifyEmails": false,
"maxConcurrency": 5,
"maxWebsitesConcurrency": 5,
"requestTimeoutSecs": 15,
"maxSiteDurationSecs": 90,
"respectRobotsTxt": true,
"jsFallback": false,
"useProxy": false,
"proxyConfiguration": {
"useApifyProxy": true
},
"verificationLevel": "mx",
"smtpFromAddress": "[email protected]",
"includePersonalNames": true
}
}
cURL
curl -X POST https://extracto.cloud/api/v1/parsers/website-contact-extractor \
-H "X-API-KEY: ext_your_api_key" \
-H "Content-Type: application/json" \
-d '{"input":{"urls":["konditorei-buchwald.de"],"maxPagesPerSite":20,"verifyEmails":false,"maxConcurrency":5,"maxWebsitesConcurrency":5,"requestTimeoutSecs":15,"maxSiteDurationSecs":90,"respectRobotsTxt":true,"jsFallback":false,"useProxy":false,"proxyConfiguration":{"useApifyProxy":true},"verificationLevel":"mx","smtpFromAddress":"[email protected]","includePersonalNames":true}}'