Lead List Cleaner
Pricing
from $8.50 / 1,000 cleaned lead rows
Lead List Cleaner
Turn any scraper's raw dataset into a clean, scored lead list — dedupe rows, kill dead emails and dead domains, rank the rest by hotness. Runs standalone or as an Integration bolted onto Google Maps / lead scrapers.
Pricing
from $8.50 / 1,000 cleaned lead rows
Rating
0.0
(0)
Developer
Tim Zinin
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Turn any scraper's raw lead dump — a Google Maps export, a directory scrape, a CSV turned into JSON — into a clean, deduped, scored list ready to work. Every email gets a real syntax + MX check, every domain gets a real DNS-alive check, duplicates collapse into one row, and what's left is ranked by hotness so you know who to call first.
What you get
- Works on any row shape — no fixed schema. Point it at a Google Maps scraper export, a CSV turned into JSON, or a pasted list — it finds the email, domain, name and phone wherever the source row put them.
- Duplicate leads (same domain, or same email when there's no domain) collapse into one row, so you never pay to see the same company twice.
- Every email gets a real syntax + MX check, not a regex guess — dead addresses, role
accounts (
info@,support@...) and known disposable domains are all flagged. - Every domain gets a real DNS alive/dead check — a company that folded two years ago doesn't make your call list.
- A 0-100 hotness score for what's left, so the leads worth calling first float to the top.
- Runs standalone, or as an Integration bolted straight onto the scraper you already use — see "How to run it" below.
- Runs on Apify: schedule it, monitor it, call it from the API, export to JSON/CSV/Excel or push straight into your own pipeline.
How to run it
Three ways in — pick whichever fits your workflow.
- Paste rows directly. Click Try for free, paste your lead rows into Items — any shape works — and hit Start.
- Point it at a dataset. Leave Items empty, use the Dataset ID picker to select another Actor's run (or a public JSON URL into Dataset URL), then hit Start. Picking the dataset through that field — not just having its ID — is what grants this run read access to it; a dataset you didn't pick there is only reachable if it's public.
- Add it as an Integration — how most buyers will actually use this. Open the
scraper Actor you already run (a Google Maps scraper, a directory scraper, anything
that produces a dataset of leads), go to its Integrations tab, click Add
integration, and pick Lead List Cleaner. Leave Dataset ID empty in the
integration's prefilled input — this Actor reads it automatically from the
triggering run. From then on, every successful run of that scraper feeds straight
into this one, no manual step required. One honest caveat: what actually grants
access to another Actor's dataset is picking it through the Dataset ID field —
that's a real permission grant, not just an ID. The raw
payload.resource.defaultDatasetIdthis Actor also reads as a fallback carries no such grant, so it only succeeds when the triggering dataset happens to be public; on a private dataset it fails, and you'll see that failure spelled out in the run's free output row rather than a silent empty result.
Pricing
Pay-per-event: $0.005 per run start + $0.01 per cleaned lead delivered. No monthly seat, no minimum. 100 leads cost about $1.01; 1,000 leads about $10.01.
Both prices step down automatically with Apify's own account tier — from $0.005/$0.01 on the FREE plan to $0.004/$0.008 on DIAMOND (up to 20% off, the standard discount ladder every Actor in this fleet runs on, not a volume discount tied to how many leads you clean) — this Actor's own Pricing tab shows what applies to your account.
The first 25 clean leads of every run are free — no card, no commitment, see it work on your own data before you pay for a single row. Rejected rows (duplicates, dead emails, dead domains, below your score cutoff) and the final summary row are always free, on every run — you only ever pay for a lead that actually passed.
Input
| Field | Required | What it does |
|---|---|---|
items | no | Raw lead rows to clean directly — any shape (a Google Maps scraper export, a CSV turned into JSON, anything with an email/website/phone somewhere in it). Takes priority over datasetId and datasetUrl. |
datasetId | no | Another Actor's dataset to clean, chosen through the dataset picker (not a plain text ID) — picking it there is what grants this run read access to it. Filled in automatically when this Actor runs as an Integration — leave it empty in that setup. |
datasetUrl | no | Public URL returning a JSON array of lead rows. Used only when both items and datasetId are empty. |
checks | no | Pipeline stages to run: dedupe, email, domain, score. Default: all four. |
minScore | no | Drop leads scoring below this (0-100) from the clean, billed output. They still come back as free rejected rows with a reason. Default 0. |
maxRows | no | Safety cap on how many input rows this run will process, regardless of source. Default 1000. |
{"items": [{ "title": "Shopify", "categoryName": "Software company", "website": "https://www.shopify.com", "phone": "+1 888-746-7439", "url": "https://www.google.com/maps/place/Shopify" },{ "name": "HubSpot", "email": "press@hubspot.com", "phoneNumber": "+1 888-482-7768", "link": "https://www.hubspot.com" },{ "companyName": "Zoom Video Communications", "description": "Video conferencing platform. Contact us at info@zoom.us for enterprise sales.", "domain": "zoom.us" },{ "title": "Shopify Inc", "website": "https://shopify.com", "phone": "+1 888-746-7439" },{ "title": "Stripe", "categoryName": "Payment processor", "website": "https://stripe.com", "phone": "+1 888-926-2289", "emails": ["support@stripe.com"] }],"checks": ["dedupe", "email", "domain", "score"],"minScore": 0,"maxRows": 1000}
Output
One dataset row per clean lead — this is a real row from a real run of the exact input above:
{"name": "Stripe","email": "support@stripe.com","domain": "stripe.com","phone": "+1 888-926-2289","sourceUrl": null,"raw": { "title": "Stripe", "categoryName": "Payment processor", "website": "https://stripe.com", "phone": "+1 888-926-2289", "emails": ["support@stripe.com"] },"emailStatus": "valid","emailFlags": ["role:support"],"domainStatus": "alive","techStack": [],"hotnessScore": 70,"scoreReasons": ["valid email +40", "alive domain +30"],"found": true,"checkedAt": "2026-08-03T15:11:21.886Z"}
The fourth row of that same run — "Shopify Inc" — shares its domain with the very first row ("Shopify"), so it comes back as a free, rejected duplicate instead:
{"name": "Shopify Inc","email": null,"domain": "shopify.com","phone": "+1 888-746-7439","sourceUrl": null,"raw": { "title": "Shopify Inc", "website": "https://shopify.com", "phone": "+1 888-746-7439" },"emailStatus": null,"emailFlags": [],"domainStatus": null,"techStack": [],"hotnessScore": 0,"scoreReasons": [],"rejected": true,"rejectReasons": ["duplicate"],"found": false,"checkedAt": "2026-08-03T15:11:21.885Z"}
The final row of every run is a free summary, never billed — for this same 5-row input:
{"rowType": "summary","totalIn": 5,"clean": 4,"rejected": 1,"billed": 0,"freeDemo": 4,"pushFailed": 0,"rejectReasons": { "duplicate": 1 }}
| Field | Meaning |
|---|---|
name / email / domain / phone / sourceUrl | Normalized from whatever fields the source row actually had. null when that piece wasn't found anywhere in the row. |
emailStatus | valid (syntax + MX both check out), dead (confirmed: bad syntax, the domain doesn't exist, or it can't receive mail at all), risky (a DNS resolver hiccup — not a confirmed answer either way), or null when there was nothing to check (no email found in the row, or email wasn't in checks). |
emailFlags | role:info / role:support / etc. for shared-inbox addresses, disposable for a known temp-mail domain. Both are free bonus signals from a static list, not exhaustive. |
domainStatus | alive (resolves, or has working mail servers), dead (confirmed: no DNS records at all), unknown (a resolver hiccup, never treated as dead), or null when there was nothing to check. |
hotnessScore | 0-100, additive: +40 valid email, +30 alive domain, +15 a non-role email address. 0 (with scoreReasons: []) if the score stage wasn't included in checks. |
scoreReasons | Plain-text breakdown of exactly which bonuses added up to hotnessScore. |
techStack | Reserved for a future check — currently always []. The +15 scoring bonus for tech signals is wired up and ready for when it ships. |
rejected / rejectReasons | Present only on filtered-out rows. Reasons: duplicate, dead-email, dead-domain, below-min-score, no-contact-signals (no email AND no domain were found anywhere in the row — nothing here to ever contact). |
found | true for a clean, billed lead; false for a rejected row or a processing error. |
raw | The original row, completely untouched, for anything not extracted above. |
billed + freeDemo (summary row) | How many of clean were actually charged vs. delivered free under the 25-lead demo cap. |
pushFailed (summary row) | Always 0 on a normal run — rises only if a row genuinely couldn't be delivered to your dataset. |
Related tools
Related tools for adjacent workflows in B2B lead generation and data enrichment.
| Actor | What it does |
|---|---|
| B2B Lead Enricher | Pair it in the B2B lead generation and data enrichment workflow: Turn a list of company websites into sales-qualified lead cards: detected tech stack, a rough revenue... |
| Company Profile Lookup | Pair it in the B2B lead generation and data enrichment workflow: Turn a domain or company name into one unified company card: website tech stack (CMS, ecommerce, key tech)... |
| Company Registry Enricher | Pair it in the B2B lead generation and data enrichment workflow: Turn a company name, LEI or UK company number into an official registry card: legal name, status,... |
| Structured Data Extractor | Pair it in the B2B lead generation and data enrichment workflow: Turn any URL into clean structured JSON — title, description, image, JSON-LD, headings, links, emails and... |
| Company Hiring Radar | Pair it in the B2B lead generation and data enrichment workflow: Pull every open role a company is hiring for from its public job board (Greenhouse, Lever, Ashby) and turn... |
FAQ / Limitations
Does it need an API key, a login, or a browser? No — just DNS lookups, and optionally reading a dataset or URL you point it at. No proxies, no browser, no scraping of its own.
Why did a real company's email or domain come back risky / unknown? That means
the DNS resolver didn't return a confirmed answer within the 12-second timeout — not
that the address or domain is actually bad. A confirmed dead verdict (no such domain,
or no mail servers at all) is a different, stronger signal, and the two are never
collapsed into each other.
Why doesn't domainStatus flag a for-sale or placeholder page as dead? It's a
live/dead call from DNS records alone — alive, dead, or unknown. This Actor
doesn't fetch the homepage or judge what's actually being served; a domain that
resolves comes back alive even if nobody's home yet.
My lead has a non-Latin or accented email address and it came back null. Email
extraction currently matches ASCII addresses only — an internationalized (Unicode)
local part or domain in the source text won't be picked up. Put it in a dedicated
email field and it will still be tried, but a genuinely non-ASCII address itself
won't match.
Does a personal Gmail / Yahoo / Outlook / mail.ru address count as the lead's own
domain? No. Free-mail providers are excluded from domain extraction on purpose — two
different companies both using @gmail.com must never collapse into one deduped
"lead," and a free-mail provider's own domain is always alive regardless of who's
behind the address.
What happens if my source is empty? A soft success, not a failure — you get one free explanatory row telling you which input source was empty or missing, and why. No charge beyond the automatic run-start fee.
Who pays when this runs as an Integration, triggered by another Actor's run? Apify's own documentation doesn't spell this out explicitly for third-party Integrations, and we haven't had this confirmed by Apify yet. Our working assumption, until it is: the account that configured the Integration is billed. We'll update this line the moment we have a confirmed answer.
Can I call it from an AI agent? Yes — standard Apify Actor, callable from the Apify API, the SDK, or the Apify MCP server.
What this is NOT. Not a live mailbox check — no SMTP conversation, ever;
emailStatus: valid means the domain can receive mail, not that one specific inbox
exists. Not a company-enrichment tool — there's no tech-stack, firmographic or social
lookup here, just contact-signal validation and scoring. Not a web scraper — it never
fetches a lead's own website.
Found a wrong result, or need a check we don't run? Open an issue on this Actor's page.