Verified Website Contact Finder π§ No Placeholder Emails
Pricing
from $10.00 / 1,000 domain enricheds
Verified Website Contact Finder π§ No Placeholder Emails
Find real company emails, phone numbers and social profiles from any website. Pass domains or URLs; role and personal emails are returned separately, and placeholder addresses other scrapers return are filtered out. Bulk domains per run, export as JSON or CSV.
Pricing
from $10.00 / 1,000 domain enricheds
Rating
0.0
(0)
Developer
Yaniv van der Stigchel
Maintained by CommunityActor stats
1
Bookmarked
2
Total users
1
Monthly active users
6 hours ago
Last modified
Categories
Share
Website Contact Finder β real contacts, not placeholders
Extract company emails, phone numbers and social profiles from any website. Every address is labelled and filtered, so you get contacts you can actually use.
What it does
- Extract emails from a list of websites
- Find company contact details in bulk
- Get emails and phone numbers from domains
- Enrich a domain list with contact info
- Scrape contact pages and Impressum details
The problem it solves
Most contact scrapers return whatever looks like an email address. Tested against real company homepages, that includes:
| Address | Where it came from |
|---|---|
damian.michelfelder@example.com | Stripe's documentation samples |
billing@example.com | Stripe's docs |
sara.baker@acme.com | Mollie's marketing mockup |
zoya@lumos.com | Mollie's mockup, invented brand |
logo@2x.png | an image filename |
None of those are contacts. A scraper that returns them hands you a list that looks like data and is not. This one filters them out.
Every address is labelled
| Field | Meaning |
|---|---|
type: "role" | info@, sales@, support@ β a company contact point |
type: "personal" | an individual's address |
confidence: "high" | role address on the company's own domain |
confidence: "medium" | personal address on the company's own domain |
confidence: "low" | address on a different domain |
source | the exact page it was found on |
Personal addresses are off by default. Role addresses are company contact points; personal ones are individuals' data and carry GDPR obligations for you as the data controller.
Honest reporting
reason | Meaning | Charged |
|---|---|---|
β (ok: true) | Contacts found | Yes |
no-contacts-published | Site reachable, publishes nothing | No |
domain-not-resolving | The domain does not resolve β expired or mistyped | No |
site-blocked | Site is up and refuses automated clients | No |
site-timeout | No answer in the time budget β worth one retry | No |
site-tls-error | Certificate or handshake failure, often expired | No |
site-error | Server error on their side, may be temporary | No |
site-unreachable | Anything else | No |
Many companies use contact forms and publish no address at all. You get told that, and you are not charged for it.
The failure codes are split on purpose. Measured on a 400-domain sample
(3 September 2026): of the rows a single site-unreachable code used to cover,
35% did not resolve, 28% answered with a 4xx, 15% answered normally on retry,
11% failed on TLS and 7% timed out. Each of those wants a different response β
drop the dead domain, reach the blocked one another way, retry the flaky one β
and one code could not tell you which you had.
Input
| Field | Required | Description |
|---|---|---|
domains | yes | Domains or URLs |
includePersonal | no | Default false |
maxConcurrency | no | 1β20, default 5 |
proxy | no | Optional |
Output
Every row has the same fields whether it succeeded or failed, so you can select columns without branching. Failed rows are never charged.
| Field | Type | Description |
|---|---|---|
success | boolean | True when this row carries data. Failed rows are never charged. |
domain | string | The website this row is about. |
roleEmails | array | On-domain role addresses (info@, sales@, support@) β the company's own contact points. |
personalEmails | array | On-domain addresses belonging to named individuals. Different reliability, and different legal handling under GDPR. |
otherDomainEmails | array | Addresses on a different domain to the site β usually the agency that built it. Kept separate rather than presented as the company's contact. |
phones | array | Phone numbers published on the site. |
socials | array | Links to the company's social profiles. |
emailCount | integer | Total addresses found, including off-domain ones. |
pagesFetched | integer | How many pages of the site were crawled to produce this row. |
errorCode | string | Machine-readable failure reason. Null on success. |
errorMessage | string | Human-readable explanation of the failure. Null on success. |
Example β success
{"success": true,"domain": "stripe.com","roleEmails": ["info@stripe.com","sales@stripe.com"],"personalEmails": ["dana.olsen@stripe.com"],"otherDomainEmails": ["hello@buildagency.example"],"phones": ["+1 888 963 8955"],"socials": ["https://twitter.com/stripe","https://www.linkedin.com/company/stripe"],"emailCount": 4,"pagesFetched": 6,"errorCode": null,"errorMessage": null}
Example β failure
A failure carries the same fields, so nothing downstream has to branch.
{"success": false,"domain": "contact-form-only.example","roleEmails": null,"personalEmails": null,"otherDomainEmails": null,"phones": null,"socials": null,"emailCount": null,"pagesFetched": 5,"errorCode": "no-contacts-published","errorMessage": "The site was reachable but publishes no email, phone, or social profile. Many companies only offer a contact form."}
Error codes
domain-not-resolvingsite-blockedsite-errorsite-tls-errorsite-timeoutsite-unreachableno-contacts-publishedunparseable-domainerror
Use it for
- Role emails from a website β info@, sales@, support@, separated from personal
- Company contact details β emails, phones and social profiles per domain
- Lead lists and CRM enrichment from a domain list
- Sales prospecting without placeholder junk
- Partner and vendor research
Use it from an AI agent (MCP)
This Actor is callable as a tool through the Apify MCP server, so Claude, ChatGPT, Cursor and VS Code can run it directly.
Add the server to your MCP client:
{"mcpServers": {"apify": {"url": "https://mcp.apify.com","headers": {"Authorization": "Bearer <YOUR_APIFY_TOKEN>"}}}}
Then ask for what you want in plain language β for example βfind the contact details published on this company websiteβ β and the agent calls cleanfeed/verified-website-contacts with the right input. Every output field is described in the dataset schema, so the agent knows what it is getting back before it runs anything.
Call it from code
Python
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("cleanfeed/verified-website-contacts").call(run_input={"domains": ["https://stripe.com"],})for item in client.dataset(run["defaultDatasetId"]).iterate_items():if item["success"]:print(item)
JavaScript
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('cleanfeed/verified-website-contacts').call({domains: ["https://stripe.com"],});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items.filter((i) => i.success));
cURL
curl -X POST "https://api.apify.com/v2/acts/cleanfeed~verified-website-contacts/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \-H 'Content-Type: application/json' \-d '{"domains": ["https://stripe.com"]}'
Related actors
| If you need | Use |
|---|---|
| Company data from YouTube channels instead | YouTube Channel Transcript Scraper |
Limitations
- Many companies publish no contact details at all, using a form instead. Those return
errorCode: no-contacts-published, which is an honest answer rather than a failure. - Off-domain addresses are separated, not discarded. An address on another domain is usually the agency that built the site, so it lands in
otherDomainEmailsrather than being presented as the company's contact. - Placeholder addresses are filtered β
example.com,acme.com,yourdomain.comand asset filenames such aslogo@2x.pngthat match an email pattern. - Personal addresses are returned separately from role addresses and are off by default; under GDPR they carry different handling obligations.
FAQ
How is this different from a regex over the page?
A regex also returns fake data. Placeholder domains (example.com, acme.com), asset filenames like logo@2x.png, and the web agency's own address all match an email pattern. Those are filtered or separated here.
What is the difference between role and personal emails?
Role addresses (info@, sales@, support@) are company contact points. Named addresses belong to individuals β different reliability, and different handling under GDPR. They are returned in separate fields, and personal ones are off by default.
Why did a domain return nothing?
Plenty of companies publish no address and use a contact form instead. That returns errorCode: no-contacts-published, which is a real answer β a guessed address that bounces costs you sender reputation.
What are otherDomainEmails?
Addresses found on the site but hosted elsewhere, typically the agency that built it. They are kept rather than dropped, but not presented as the company's own contact.
Can I process a list of domains?
Yes. Pass the whole list and tune maxConcurrency; one row is returned per domain, including the ones with nothing published.
Notes
Only publicly published company contact details are collected, from pages the company itself serves (homepage, contact, about, Impressum, legal). No login, no purchased databases, no guessed addresses.