Lead List Cleaner avatar

Lead List Cleaner

Pricing

from $8.50 / 1,000 cleaned lead rows

Go to Apify Store
Lead List Cleaner

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

Tim Zinin

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

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.

  1. Paste rows directly. Click Try for free, paste your lead rows into Items — any shape works — and hit Start.
  2. 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.
  3. 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.defaultDatasetId this 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

FieldRequiredWhat it does
itemsnoRaw 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.
datasetIdnoAnother 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.
datasetUrlnoPublic URL returning a JSON array of lead rows. Used only when both items and datasetId are empty.
checksnoPipeline stages to run: dedupe, email, domain, score. Default: all four.
minScorenoDrop leads scoring below this (0-100) from the clean, billed output. They still come back as free rejected rows with a reason. Default 0.
maxRowsnoSafety 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 }
}
FieldMeaning
name / email / domain / phone / sourceUrlNormalized from whatever fields the source row actually had. null when that piece wasn't found anywhere in the row.
emailStatusvalid (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).
emailFlagsrole: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.
domainStatusalive (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.
hotnessScore0-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.
scoreReasonsPlain-text breakdown of exactly which bonuses added up to hotnessScore.
techStackReserved for a future check — currently always []. The +15 scoring bonus for tech signals is wired up and ready for when it ships.
rejected / rejectReasonsPresent 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).
foundtrue for a clean, billed lead; false for a rejected row or a processing error.
rawThe 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 for adjacent workflows in B2B lead generation and data enrichment.

ActorWhat it does
B2B Lead EnricherPair 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 LookupPair 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 EnricherPair 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 ExtractorPair 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 RadarPair 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.


Built by zinin. Questions? Telegram @timzinin.