Building a table of local business leads usually means chaining several tools: one to discover the businesses in a city, one to find each business's website, one to pull an email off that site, and a verifier so you are not loading dead mailboxes into your CRM. Each category tends to need its own scraper, each scraper needs a proxy budget, and the output columns shift every time one link in the chain changes.
This post walks through one Apify actor, Local Business Leads Scraper by flash_scraper, that collapses that stack into a single actor call. It discovers businesses on OpenStreetMap, crawls each business's own public website, MX-verifies the email it finds and scores every row 0-100, with no API key, no proxy and no login. Used as a business email finder it returns not just the address but whose mailbox that address is. Everything below comes from the actor's README, its dataset schema and one real throwaway run; nothing is estimated.
What you get
One row per business, 73 stable columns wide, exported as CSV, JSON or Excel. The dataset schema describes a row as "identity, location, phone, MX-verified email, socials, website platform and marketing tech, a 0-100 lead score with an A-F grade, and the OpenStreetMap provenance of the row." It covers 95 curated categories (220+ terms) in any city on earth. The eight columns you will read first:
| Column | Title | What the schema says it holds |
|---|---|---|
name |
Name | Business name |
phone |
Phone | Primary phone — the OSM tag, and only the OSM tag; never filled from the site |
email |
Primary contact email, chosen by mailbox ownership then deliverability | |
email_status |
Email status |
deliverable / risky / undeliverable when verification is on and a candidate exists; missing when no email was found |
email_type |
Mailbox owner |
own_domain / free_mail / third_party / unknown — third_party means the address belongs to the business's marketing agency or web designer |
website_platform |
Website platform | CMS / site builder: WordPress, Wix, Shopify, Squarespace, Webflow, GoDaddy, Weebly, Duda and more |
lead_score |
Lead score | 0-100 completeness/reachability score |
lead_grade |
Grade |
A ≥80, B ≥65, C ≥50, D ≥35, else F
|
Two of those deserve a note. email_type decides whether a lead is usable at all: the schema says a third_party address "is deliverable but does not reach the business, and it is scored at half weight." email_status is the result of four checks that run over DNS and never over SMTP: syntax, a mail-server lookup on the domain, a role-address check and a disposable-domain list. No mailbox is ever probed, which is what keeps verification proxy-free and included in the price.
Every row also carries google_maps_url (a constructed search link; nothing is scraped from Google to build it), osm_url pointing at the source OpenStreetMap element, and attribution, the ODbL string the licence requires you to keep with an exported CSV.
A real run
A throwaway run from 2026-09-21, minimal input, no filters:
input: category 'dentist', location 'Austin, Texas', maxItems 10
run: SUCCEEDED 16.9s
STATUS MESSAGE: Done — 10 leads delivered for dentists in Austin, Texas. 1 with email (10%), 2 with phone (20%), 2 contactable (20%). 8 of these 10 row(s) have no phone, no email and no social profile — add requireAnyContact: true to drop them before they are charged.
The status message is the honest part. A bare API call keeps every mapped location, including the ones with nothing to contact, and the actor says so and names the switch that fixes it. The README's filter-off benchmark reports the same at larger scale: on a bare {} run of 100 Austin dentists, "50 of the 52 rows with no website had no phone, no email and no social profile either." The Console form pre-fills onlyWithWebsite and pre-ticks requireAnyContact; API calls and schedules leave both off so existing inputs are unchanged.
With onlyWithWebsite: true, the README's reference run (dentist / Austin, Texas, 2026-08-08, n=55) measured a phone on 96% of rows, an MX-verified email on 55%, a detected website platform on 71% and a website on 100%. With every filter off (n=100) the same city measured 48% phone, 26% email and 34% platform.
The Python call
Copied from the README's "Smallest useful call"; the same JSON works in the Console, the REST API and n8n/Make/Zapier:
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("flash_scraper/local-business-leads").call(run_input={"category": "dentist", "location": "Austin, Texas", "maxItems": 10, "crawlEmails": False})
rows = client.dataset(run["defaultDatasetId"]).list_items().items
The sample sets crawlEmails: False, which the README measured locally on 2026-08-29 at 10 rows in 17 s for $0.03 at the free-plan rate. For a normal run, remove that key or set it to True as the README's fuller input examples do; the defaults are website crawling on, email verification on and 3 pages per website. An API call that sends no maxItems gets 100; the Console form starts at 25.
From there the pipeline is filters. onlyWithWebsite, onlyWithoutWebsite, excludeKeywords, excludeChains and skipClosed are decided from the map data alone, before any website is crawled; onlyWithEmail, onlyVerifiedEmail, requirePhone, requireSocial and requireAnyContact are applied as sites are crawled. Every one drops the row before it is pushed and before it is charged, so maxItems means "this many rows I can use", and the run log names each filter and how many rows it removed. outputFields narrows the columns; name and attribution are always included.
What it costs
From the README's pricing section: "$5 per 1,000 delivered leads, which is $0.005 per lead on the free plan and less on paid plans." Read from the live pricing on 2026-09-14: $0.005 per delivered lead on the free plan and $0.0045 (Bronze) down to $0.0025 (Diamond) on paid plans. You are charged for delivered businesses only, not for API calls or compute, there is no subscription, and the Pricing tab is authoritative. MX verification, mailbox-ownership classification and lead scoring are included in the single per-lead rate; filtered rows are dropped before billing; a run that finds nothing charges nothing.
The README's own arithmetic: the Console form as it opens (cap 25) costs at most $0.125 in leads, and 500 delivered rows cost $2.50; on the website-filtered reference run about 55% carried an email, so 500 rows is roughly 275 with an email. Since filters run before billing, onlyWithEmail: true keeps the bill to rows that carry an email.
What it does not do
Copied from the README's own limits section:
- It does not scrape Google Maps. Listings come from OpenStreetMap, so there are no Google star ratings and no Google review counts.
-
The
ratingandreview_countcolumns are sparse. They exist only when a business publishes a rating in its own website markup, measured at about 7% of rows (4 of 55 on the 2026-08-08 reference run), and they are never a Google rating. SetminRatingorminReviewCountand rows with no rating are dropped, not kept. - Van-based trades are thinly mapped. OpenStreetMap held 171 dentists in the Austin bounding box but 9 plumbers and 5 electricians. A metro of a million people can return single digits for those categories.
-
A guessed email is never sold as a verified one. A pattern-guessed address stays in
email_guessand is never promoted intoemail.
Three more constraints to script around. A multi-search run is capped at 25 category x location combinations; above it the run fails before any network request and before any charge. Verification never contacts a mail server, so read deliverable as "the domain accepts mail and the address is not a known-bad shape": a catch-all domain, or a mailbox deleted while the domain kept its mail servers, can still grade deliverable. And the listings are ODbL data, so keep the source and attribution columns if you redistribute or publish the rows.
Try it
One quick way to see the columns is the README's "Try it" example task, capped at 25 leads since 2026-09-01: Find local business leads with emails by category. The actor itself, with its Pricing tab, is at https://apify.com/flash_scraper/local-business-leads. Start at the form's cap of 25, read the status message, then raise maxItems once the first run looks right.
Top comments (0)