Porch.com Contractor Scraper — Home Service Pro Leads
Pricing
$3.50 / 1,000 per pro returneds
Porch.com Contractor Scraper — Home Service Pro Leads
785,653 home-service pros from Porch.com: business name, trade, phone (99.7% fill), website, street address, lat/lon, rating, reviews, years in business, full services list, ZIP service area and cached state licence records. 131 trades, all 50 states. Filter by trade, state, city or rating.
Pricing
$3.50 / 1,000 per pro returneds
Rating
0.0
(0)
Developer
Scrapers Delight
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
7 days ago
Last modified
Categories
Share
Home-service professionals from Porch.com's public pro directory. One row per contractor, carrying businessName, trade, phone, website, street, city, state, zip, latitude/longitude, rating, reviewCount, startYear/yearsInBusiness, the pro's full declared services list, their ZIP-level serviceArea, and any state licence records Porch holds (number, type, issuing board and the board's own lookup URL).
Filter by trade, state, city, star rating, review count, years in business or whether the pro has claimed their listing — or paste profile URLs straight in. No login. No cookies. No CAPTCHA solving. Porch never challenges a request; it only rate-limits, and the Actor handles that.
How big is it
785,653 pro profiles. That is not a marketing figure — it is the exact count from walking all 32 of Porch's child sitemaps end to end on 2026-08-12 (31 × 25,000 + 10,653), with zero duplicate URLs across the whole set.
Inside it: 131 trades, 50 states + DC + Puerto Rico, Guam, the US Virgin Islands and the
Northern Mariana Islands, and 20,197 distinct city+state combinations (plus one non-geographic
national bucket holding 103 profiles that carry no state). The ten biggest trades,
counted over all 785,653 URLs:
| Trade | Profiles | Trade | Profiles | |
|---|---|---|---|---|
| General Contractors | 141,500 | Remodeling Contractors | 34,510 | |
| Electricians | 60,430 | HVAC Contractors | 32,334 | |
| Plumbers | 51,920 | Roofers | 31,465 | |
| Handymen | 36,368 | Landscapers | 27,891 | |
| Painters | 35,777 | Garage Door Specialists | 17,777 |
Biggest states: CA 121,855 · TX 81,180 · FL 64,665 · NC 30,450 · NY 29,932 · OH 26,662. Biggest cities: Houston 8,486 · Los Angeles 6,498 · San Antonio 5,783 · Chicago 5,230 · Austin 4,997. Every one of those counts is in the trade and state dropdowns, so you can size a run before you pay for it.
Quick start
Click Try for free and hit Start. This is the input the Actor ships with — no edits needed:
{"trades": ["roofers"],"states": ["TX"],"maxItems": 50,"proxyConfiguration": { "useApifyProxy": true }}
That exact run, executed twice on 2026-08-12: 50 rows in 30 and 34 seconds, 57 pages fetched each time, 0 failures.
Start a run that names no trade, state, city or profile URL — an empty {} from an
API call or agent, say — and the Actor runs that same sample (50 roofers in Texas, $0.18) and
logs which inputs to set. It never buys a random nationwide slice on your behalf.
- Pick one or more Trades from the dropdown (each shows its live profile count).
- Pick one or more States (same — each shows its count). Or add Cities for a tighter slice.
- Set Max results — that is your hard cost cap.
- Start. Rows are pushed to the dataset in batches of 500 as they are collected; export CSV / Excel / JSON.
- To page further next time, set Skip to the number of profiles the last run scanned (with no lead-quality filter on, that is the number of rows you bought).
The wedge: the fields the profile page never prints
Every Porch profile server-renders its whole application state into the page. That blob — not the visible HTML — is what this Actor reads, and it carries five things you cannot get by looking at the page or by scraping Google Maps:
- The FULL services list. The page shows 5 and hides the rest behind "+ Show all services". One pro in the 399-profile sample declared 476 services; the median pro who publishes any declares 40. That is the list that tells you whether a roofer also does gutters and attic insulation.
- The FULL postal service area. Not "serves Austin" — the actual ZIP list. Median 46 ZIPs, widest in the sample 872 ZIPs across 595 named cities. This is the field that tells you whether a pro can actually take a job at a given address.
- Latitude and longitude on 100% of rows, alongside the street address (95.5%).
startYear, soyearsInBusinessis computed rather than guessed (58.1% of profiles).- State licence records with the issuing board's own lookup URL — see below.
Licence records: a lead to verify, not proof
Porch caches state licence records but does not keep them current. In the 399-profile sample,
22 profiles carried a licence (5.5%), holding 25 records between them — and 24 of those 25 had
already expired. Porch's cached status read ACTIVE on 23 of the 25, and 22 records were both
ACTIVE in the cache and already expired.
So the field is named statusOnRecord, never status, and every record ships an expired
boolean computed against today's date. What is genuinely useful is the number, the type, and the
authority — they tell you exactly which board to re-verify against, and each record carries that
board's lookup URL:
{"number": "NAT-F158526-1","type": "Lead Abatement (Firm - Certification)","statusOnRecord": "ACTIVE","expiresOn": "2020-11-05","expired": true,"verifiedBySource": false,"authority": "Texas Department of State Health Services","authorityUrl": "https://vo.ras.dshs.state.tx.us/datamart/selSearchTypeTXRAS.do?from=loginPage"}
verifiedBySource: true means Porch got the record from the state registry rather than the pro's own
claim — that was true for only 4 of the 25 records in the sample. If you need licence data you can
act on, use our California CSLB or
Florida DBPR Actors, which read the
state registries directly, and use this Actor for the contact and coverage fields.
Read this before you buy rows
Five things that would otherwise turn into a support ticket. All measured on 399 live profiles, 2026-08-12, drawn from 8 of the 32 child sitemaps (47 states, 72 trades).
- Porch never publishes an e-mail address.
emailisnullon 100% of rows.hasEmailOnFile(79.4% true) tells you Porch holds one; the address itself is not on the page and nothing can produce it from this source. See "Honest limits" for what to do instead. - Two kinds of listing share one schema. 76.4% are claimed accounts a pro maintains; 23.6% are stubs Porch built itself. They are very different leads and the headline count hides it.
- Rating fill is inverted against claimed status. The stubs carry a rating 76% of the time;
the claimed accounts only 19%. So
claimedOnly+ a rating floor is a narrow, expensive combination — measured, it leaves 11.3% of the directory and fetches 8.9 pages for every row you keep. Legitimate, but budget the time for it. - Cached licence status is stale — 24 of 25 records already expired. Read
expired, notstatusOnRecord. - A small unfiltered run is not a representative sample of the directory. Results follow Porch's own sitemap order, and the sitemaps are not shuffled: child sitemap 0 is 5.4% general contractors, child sitemap 31 is 36.1%. If you want a representative trade mix, select the trades rather than taking the first N rows.
What you get — one row per pro
Real values, taken from the sample row further down (Clear Choice Roofing, Austin TX, captured 2026-08-12).
Identity & trade
| Field | Example | Notes |
|---|---|---|
profileUrl | https://pro.porch.com/austin-tx/roofers/clear-choice-roofing-1/pp | Canonical Porch URL |
companyId | 3295 | Porch's own id — the dedupe key |
businessName | Clear Choice Roofing | |
trade | Roofing Contractor | The pro's primary trade, singular |
tradeCategory / tradeSlug | Roofers / roofers | The slug is the value in the Trades dropdown |
secondaryTrades | [] | Present on 16.0% of profiles |
Contact
| Field | Example | Notes |
|---|---|---|
phone | (512) 712-4906 | Normalised to (NNN) NNN-NNNN |
website | http://www.clearchoiceroofingatx.com | Bare hosts are normalised to a URL |
email | null | Always null. Porch publishes none. |
hasEmailOnFile | true | Porch holds one; it is not on the page |
Address & geo
| Field | Example | Notes |
|---|---|---|
street / street2 | 13377 Pond Springs Rd Ste 105 / null | street2 is rare (2 of 399) |
city / state / zip | Austin / TX / 78729 | 100% fill |
latitude / longitude | 30.44978 / -97.78359 | 100% fill, numbers not strings |
Ratings & reviews
| Field | Example | Notes |
|---|---|---|
rating | 4.79 | 0–5 scale, 2 decimals. null = Porch published none |
reviewCount | 367 | Aggregated across all sources Porch pulls from |
reviewSourceCount | 4 | How many review sites fed that aggregate |
porchReviewCount | 12 | Reviews written on Porch itself. 0 is a real value, not missing |
Credentials & status
| Field | Example | Notes |
|---|---|---|
startYear / yearsInBusiness | 1990 / 36 | Computed against the current year |
licenseNumbers / licenses | ["NAT-F158526-1"] / see above | Read expired, not statusOnRecord |
accountStatus | USER_ACCOUNT | NO_ACCOUNT | USER_ACCOUNT | VERIFIED_ACCOUNT |
isClaimed / isVerified | true / false | isVerified was true on 1.0% of the sample |
businessStatus / acceptingWork | OPEN / true | 396 of 399 OPEN, 3 UNKNOWN |
isVetted / isNationalPro | false / false | Both were false on all 399. See Honest limits |
Coverage & content
| Field | Example | Notes |
|---|---|---|
services / serviceCount | ["Attic Insulation Installation", …] / 17 | Count is returned even when the array is off |
serviceAreaCities / serviceAreaCityCount | ["Adkins", "Atascosa", …] / 64 | |
serviceAreaZips / serviceAreaZipCount | ["73301", "78680", …] / 235 | |
description | Family owned and operated and an Owens Corning… | Self-written, 59.9% fill |
photoUrl | https://imagescdn.staticp.com/… | Porch stock clip-art returns null, not a fake logo |
sourceUrl / scrapedAt | sitemap URL / 2026-08-13T01:46:06.878Z | scrapedAt is ISO-8601 UTC |
Field fill — measured on 399 live profiles
A random national walk of 399 live profiles, drawn from 8 of the 32 child sitemaps — 47 states, 72 trades, captured 2026-08-12. Sorted by fill, so the sparse fields are impossible to miss.
| Field | Fill | Note |
|---|---|---|
businessName, trade, city, state, zip, latitude/longitude, businessStatus | 100% | |
acceptingWork | 100% | Returned on every row; true on 99.7% of them |
phone | 99.7% | 1 of 399 profiles had none |
street | 95.5% | |
hasEmailOnFile (true) | 79.4% | Porch holds an address; it never prints it |
isClaimed (true) | 76.4% | |
services | 73.9% | The full list — the page itself truncates at 5 |
serviceAreaCities / serviceAreaZips | 71.9% | Median 46 ZIPs, widest 872 |
website | 65.2% | The handle to enrich e-mail from |
description | 59.9% | |
startYear / yearsInBusiness | 58.1% | |
rating / reviewCount | 32.1% | Most of the directory is unrated |
photoUrl | 25.8% | Stock Porch clip-art returns null, not a fake logo |
secondaryTrades | 16.0% | |
licenseNumbers / licenses | 5.5% | 22 profiles, 25 records, 24 expired |
isVerified (true) | 1.0% | 4 of 399 |
isVetted / isNationalPro (true) | 0.0% | Both false on all 399 |
email | 0.0% | Porch never publishes it. Nothing can change that |
The headline that could mislead you: this is not a ratings product. 67.9% of Porch profiles carry no rating at all. It is a phone-and-address product — 99.7% and 95.5%.
Claimed vs unclaimed, split
76.4% are claimed accounts (USER_ACCOUNT 301 + VERIFIED_ACCOUNT 4), 23.6% are stubs Porch built
itself (NO_ACCOUNT 94). Same 399 profiles:
| website | services | description | licence | rating | phone | |
|---|---|---|---|---|---|---|
NO_ACCOUNT — unclaimed stub, n=94 | 41% | 0% | 56% | 4% | 76% | 99% |
| Claimed account, n=305 | 73% | 97% | 61% | 6% | 19% | 100% |
Note the inversion in the rating column. The stubs are the ones Porch seeded from review aggregators, so they carry a rating far more often than pros who claimed their page. Pick one:
claimedOnly→ a fuller record (services, website, a business that answers)minRating→ social proof, mostly sitting on stub listings
The demo run, for contrast
One prefilled Texas-roofers run (n=50, 2026-08-12) measured phone 100% · street 98% · website 92% · rating 64% · years 74% · services 62% · licence 8% · claimed 66%; a second measured website 90%, years 72%, licence 6%. Much richer than the national average, because a specific trade in a big state skews to real businesses — and wobbly, because 50 rows is 50 rows. Plan against the 399-row national table, not this one. Every run prints its own measured fill in the log when it finishes.
How to run it
By trade and state (the default)
Trade, state and city are read off the profile URL before any page is fetched, so these three filters cost you nothing — no wasted request, no wasted money.
{ "trades": ["plumbers", "hvac-contractors"], "states": ["TX"], "maxItems": 2000 }
By city
{ "trades": ["general-contractors"], "states": ["TX"], "cities": ["houston", "sugar-land", "katy"], "maxItems": 1000 }
Cities are the city part of the URL slug. "Sugar Land" works too — spaces are converted for you.
By pasted profile URLs
{ "profileUrls": ["https://pro.porch.com/austin-tx/roofers/clear-choice-roofing-1/pp","https://pro.porch.com/murphy-tx/plumbers/first-class-plumbing-157145801/pp"] }
This skips the sitemap entirely — the fastest path, and the one to use for refreshing a list you already own. It overrides the trade/state/city filters. A URL that is not a Porch pro-profile URL stops the run with an error naming the bad one.
Lead-quality filters, and what they cost
These live on the profile page, so a page has to be fetched before a pro can be rejected. You are never billed for a filtered-out row — it costs run time, not money. Measured pass rates on the 399 sample:
| Filter | Keeps | Pages fetched per row kept |
|---|---|---|
claimedOnly | 76.4% | 1.3 |
withWebsiteOnly | 65.2% | 1.5 |
minYearsInBusiness: 10 | 50.4% | 2.0 |
minRating: "4" | 24.6% | 4.1 |
minReviewCount: 10 | 24.8% | 4.0 |
minRating: "4.5" | 17.0% | 5.9 |
claimedOnly + minRating: "4" | 11.3% | 8.9 |
Slimming the output
includeServices: false and includeServiceArea: false drop the two long arrays. serviceCount,
serviceAreaCityCount and serviceAreaZipCount are still returned, so you keep the signal without
the JSON. This saves dataset size, not requests — the price is identical either way.
Paging across runs
{ "trades": ["roofers"], "states": ["TX"], "maxItems": 1000, "skip": 1000 }
Results follow sitemap order, which is stable, so skip continues where the last run stopped instead
of re-buying the same pros. skip counts profiles scanned, not rows delivered. With no
lead-quality filter on, those are the same number. If you switch one on, the run scans more profiles
than it keeps, so set skip to the pages fetched figure the previous run printed in its log —
otherwise the next run restarts inside ground you already paid for. Pair it with an Apify Schedule (Actor → Schedules → e.g. 0 6 * * 1
for Monday 06:00 UTC) and raise skip by your maxItems each week to walk the directory.
Sample row
One real record from the default run, captured 2026-08-12. Long arrays are truncated here with …
and are complete in the dataset.
{"profileUrl": "https://pro.porch.com/austin-tx/roofers/clear-choice-roofing-1/pp","companyId": 3295,"businessName": "Clear Choice Roofing","trade": "Roofing Contractor","tradeCategory": "Roofers","tradeSlug": "roofers","secondaryTrades": [],"phone": "(512) 712-4906","website": "http://www.clearchoiceroofingatx.com","email": null, // always null — Porch publishes none"hasEmailOnFile": true, // Porch holds one, it is just not on the page"street": "13377 Pond Springs Rd Ste 105","street2": null,"city": "Austin","state": "TX","zip": "78729","latitude": 30.44978,"longitude": -97.78359,"rating": 4.79, // 0-5 scale, not a percentage"reviewCount": 367, // aggregated across 4 sources"reviewSourceCount": 4,"porchReviewCount": 12, // written on Porch itself"startYear": 1990,"yearsInBusiness": 36,"licenseNumbers": ["NAT-F158526-1"],"licenses": [{"number": "NAT-F158526-1","type": "Lead Abatement (Firm - Certification)","statusOnRecord": "ACTIVE", // Porch's CACHED status"expiresOn": "2020-11-05","expired": true, // computed today - this is the field to trust"verifiedBySource": false,"authority": "Texas Department of State Health Services","authorityUrl": "https://vo.ras.dshs.state.tx.us/datamart/selSearchTypeTXRAS.do?from=loginPage"}],"isVetted": false,"accountStatus": "USER_ACCOUNT","isClaimed": true,"isVerified": false,"businessStatus": "OPEN","acceptingWork": true,"isNationalPro": false,"services": ["Attic Insulation Installation", "Attic Ventilation Installation", "…"],"serviceCount": 17,"serviceAreaCities": ["Adkins", "Atascosa", "Austin", "Bastrop", "…"],"serviceAreaCityCount": 64,"serviceAreaZips": ["73301", "78680", "78682", "…"],"serviceAreaZipCount": 235,"description": "Family owned and operated and an Owens Corning 'Platinum Preferred Contractor'…","photoUrl": "https://imagescdn.staticp.com/api/image/display/custom/Clear%20Choice%20Roofing/…","sourceUrl": "https://pro.porch.com/austin-tx/roofers/clear-choice-roofing-1/pp","scrapedAt": "2026-08-13T01:46:06.878Z"}
Fields people misread:
ratingis a 0–5 scale with two decimals, not a percentage.nullmeans Porch published no rating; there is no0rating in this data.porchReviewCount: 0is a real value — the pro has reviews aggregated from elsewhere but none written on Porch.reviewCount: nullmeans no reviews anywhere.statusOnRecordis Porch's cache,expiredis the truth. 22 of the 25 licence records in the sample saidACTIVEand had already expired (24 of 25 had expired; 23 of 25 still readACTIVE).isVerifiedis Porch'sVERIFIED_ACCOUNTbadge (1.0%);isClaimedis the much broader "this pro has an account" (76.4%). They are not the same thing.
Input
Fields in the order they appear in the Console.
| Field | Type | Default | What it does |
|---|---|---|---|
trades | multi-select, 131 options | ["roofers"] (prefill) | Which trades to pull. Each option shows its live profile count. Free filter — read off the URL, no page fetched. Empty = all trades |
states | multi-select, 55 options | ["TX"] (prefill) | States, DC and territories, each with its profile count. Free filter. Empty = nationwide |
cities | string list | empty | Porch city slugs (houston, los-angeles, west-sacramento). 20,197 exist, so this is paste-in rather than a dropdown. "West Sacramento" also works. Free filter |
profileUrls | string list | empty | Paste exact profile URLs. Skips the sitemap; overrides trade/state/city. A non-Porch URL is named in the log and skipped |
claimedOnly | boolean | false | Keep only claimed accounts (76.4%). Fuller records — but they carry a rating only 19% of the time |
withWebsiteOnly | boolean | false | Keep only pros with a website (65.2%). The field to filter on before e-mail enrichment |
minRating | select | Any rating | 3.0 / 3.5 / 4.0 / 4.5 / 5.0 floor. Only 32.1% carry any rating, so a floor discards ~two thirds |
minReviewCount | number | 0 | Aggregated-review floor. Same caveat as the rating floor |
minYearsInBusiness | number | 0 | Founding-year floor. Present on 58.1%; the rest are dropped when set |
maxItems | number | 1000 (prefill 50) | Your hard cost cap. 50 = $0.18, 1,000 = $3.50, 10,000 = $35. Clamped to 50 when the run names no trade, state, city or profile URL |
skip | number | 0 | Skip this many matching profiles first — page across runs without re-buying rows |
includeServices | boolean | true | Include the full services array. Off = smaller dataset, same price, same requests |
includeServiceArea | boolean | true | Include the coverage cities + ZIPs. Off = smaller dataset, same price, same requests |
maxConcurrency | number | 8 | Parallel profile fetches. 8 is measured-optimal; higher just earns HTTP 429s |
proxyConfiguration | proxy | Apify Proxy (automatic) | Leave it on. Datacenter is enough — Porch is not bot-walled |
Trade slugs
All 131 are in the dropdown with their profile counts. The twenty biggest, if you are building input by API:
general-contractors 141,500 · electricians 60,430 · plumbers 51,920 · handymen 36,368 ·
painters 35,777 · remodeling-contractors 34,510 · hvac-contractors 32,334 · roofers 31,465 ·
landscapers 27,891 · garage-door-specialists 17,777 · home-cleaners 16,432 ·
flooring-contractors 14,424 · pest-control-contractors 12,110 · locksmiths 11,056 ·
appliance-repair-services 10,771 · window-contractors 10,635 · real-estate-professionals 10,492 ·
carpet-cleaners 9,251 · tree-contractors 9,102 · home-inspectors 8,776
The long tail runs down to soil-engineers (69), decorative-hardware-retailers (57),
earthquake-retrofitting-specialists (47) and storm-shelter-contractors (33).
Pricing
$0.0035 per pro returned — $3.50 per 1,000. Charged on the pro-scraped event. No monthly
platform fee from this Actor.
| Run | Rows | Cost |
|---|---|---|
| Default smoke test (TX roofers) | 50 | $0.18 |
| Every pro in Houston | 8,486 | $29.70 |
| Every roofer in the country | 31,465 | $110.13 |
| Every general contractor in the country | 141,500 | $495.25 |
| The entire Porch pro directory | 785,653 | $2,749.79 |
- You are charged for rows delivered, never for a page that was fetched and then filtered out. The default run fetched 57 pages and delivered 50; you pay for 50.
- The source itself has no repeats — 785,653 sitemap URLs, 0 duplicates (see Uniqueness), so a
sitemap-driven run cannot bill you twice for the same pro. The one way to double-pay is to paste
the same URL twice into
profileUrls; the Actor takes that list literally. - Rows are charged as they are pushed (
Actor.pushData(items, 'pro-scraped')), so if you hit a budget cap you get whole rows and stop — never a half-billed dataset. maxItemsis your cost dial, and it is enforced before a row is counted, so parallel workers cannot overshoot it.
Honest limits
- No e-mail addresses. None, for anybody.
emailis null on 100% of rows.hasEmailOnFileis true for 79.4% of pros, which tells you Porch has one on file and is not showing it. Anyone selling you "Porch e-mails" generated them somewhere else. What to do instead: take the 65.2% of rows that carry awebsiteand run them through Decision-Maker Email Finder, which turns a domain into named contacts with pattern-detected, MX-validated addresses. - Only 32.1% of profiles carry a rating, and
reviewCountfill is the same 32.1%. A rating floor is a legitimate filter, but it discards roughly two thirds of the directory and costs 4.1 pages per row kept at 4.0+. This is what Porch published, not a scraping failure. - Cached licence status is stale — 24 of the 25 records in the sample had expired, and only 4 of
25 came from a state registry (
verifiedBySource). Useexpired, and re-verify atauthorityUrl. For licence data you can act on, use the CSLB or DBPR Actors. isVettedandisNationalProwerefalseon all 399 sampled profiles. They are real fields in Porch's payload and they are returned honestly, but do not build a filter on them expecting volume.streetis the pro's business address, not their service area. A Houston-registered plumber may serve 200 ZIPs; that is whatserviceAreaZipsis for. Filtering bycitiesfilters on the registered address.- US only. Porch's pro directory covers the 50 states, DC and four territories. There is no Canadian or UK data here — for Canada use HomeStars.
- Sitemap order is Porch's order, not a quality ranking, and it is not shuffled — child sitemap 0 is 5.4% general contractors and child sitemap 31 is 36.1%. Select trades explicitly if you want a representative mix.
- No login, no CAPTCHA solving, no browser automation. Everything here comes from pages Porch serves anonymously and lists in its own public sitemap. There is no hidden mode that returns more.
How it works, and the transport ladder
- Read
https://pro.porch.com/sitemap-pro-profile-index.xml→ 32 child sitemaps → 785,653 profile URLs. Every URL is/{city}-{st}/{trade-slug}/{business}/pp, so state, city and trade are known before a single page is fetched — that is why those filters are free. - Fetch each matching profile page and read the server-rendered application state out of it. That
blob holds the fields the visible page never prints. JSON-LD is present too but only carries
name/phone/city — and its
addressRegionis actually the ZIP — so it is used strictly as a fallback. - No login, no cookie, no API key, no browser. The parser keys off the JSON payload, never off CSS class names (Porch's are hashed and change on every deploy).
The wall here is a rate limit, not a bot check. Porch throttles per exit IP with a short-window token bucket that returns HTTP 429, and never challenges. Measured through Apify on 2026-08-12 with retries switched off:
| Rung | Result |
|---|---|
| Direct, concurrency 8, 40 URLs @ 11.2 req/s | 40/40 = 100% |
| Direct, concurrency 12, 200 URLs @ 31.1 req/s | 68/200 = 34.0% (132 × HTTP 429) |
| Direct, concurrency 20, bucket already drained | 0/199 = 0% (199 × HTTP 429) |
| Apify Proxy, one pinned session, concurrency 8 | 40/40 = 100% @ 5.97 req/s |
| Apify Proxy, fresh session per request, concurrency 8 | 59/60 = 98.3% (the miss was a proxy 595 ECONN) |
So the Actor defaults to Apify Proxy with a new session minted for every request — each one starts on a full token bucket — and retries a failure on another fresh exit IP with exponential backoff. With that loop in place, the validation runs fetched 556/556, then 399/399, then 57/57 and 57/57 profile pages at concurrency 8 with zero failures — 1,069 consecutive successful content requests. Because Porch is not bot-walled, the cheap datacenter rung is enough: no residential bandwidth is needed and none is used.
Uniqueness
Walked all 32 child sitemaps contiguously, end to end: 785,653 URLs, 0 duplicates. Porch's sitemap is a clean enumeration — one URL per pro, no promoted repeats, no paging overlap.
That is worth stating plainly rather than dressing up: there is no dedupe pass in this Actor,
because the source does not need one. Both live runs confirmed it downstream — 399 rows → 399
distinct companyIds, and 50 rows → 50 distinct companyIds. The one exception is
profileUrls: that list is taken literally, so if you paste a URL twice you get (and pay for) two
rows.
Across runs, use skip. Sitemap order is stable, so skip: 1000 after an unfiltered
maxItems: 1000 run resumes exactly where you stopped — with a lead-quality filter on, skip by the
run's pages fetched count instead (see Paging across runs).
When a run fails
This Actor never hands you a green run with an empty dataset and no explanation. Every stop is named — in the log and in the run's status message — and nothing that was not delivered is billed:
- 0 rows emitted → stops with the counts behind it: pages fetched, network failures, dead profiles, unparseable pages and how many rows your filters removed — so you can tell a filter problem from a site change. Nothing charged.
- Sitemap index shrinks below 20 children (the live count is 32) → stops rather than shipping a partial slice of the directory. Nothing charged.
- Every child sitemap comes back empty or unreachable → stops; treat it as a format change at Porch. Nothing charged.
- No candidate matched your filters → stops with the exact scope it scanned and the correct spelling conventions for states, cities and trades.
- A bad
profileUrlsentry → named in the log and skipped; the valid URLs still run. All of them bad → stops without fetching anything. - Run time runs out (a short run timeout on a big
maxItems) → stops fetching with time in hand, pushes everything already collected, and tells you to raise the timeout or continue withskip. A partial run is delivered and billed for what it delivered, never lost. - More than 20% of pages return HTTP 200 with no parseable state → this one fails the run outright. It is the single case where a green run would be actively misleading: the pages loaded, so a silent success would look like real coverage of a thin extraction.
There is no "legitimate zero" mode here. A successful run always has rows.
Who buys this
- Building-materials and equipment distributors —
trade+serviceAreaZips+phonegives you every roofer who actually covers a target ZIP, not just the ones headquartered in it. - Franchise and roll-up acquirers in home services —
startYear,serviceCountandreviewCountare the screen; 141,500 general contractors is the funnel. - SaaS selling into trades (field-service, scheduling, invoicing) — the 76.4% claimed accounts are businesses that already maintain an online presence, which is the ICP; the 34.8% without a website are the ICP for anyone selling websites.
- Insurance and bonding brokers —
licenses[].expiredplusauthorityUrlis a renewal-outreach list with the verification path attached. - Lead-gen and home-warranty marketplaces —
serviceAreaZips+acceptingWorktells you who can take a job in a postcode today. - Local-SEO and reputation agencies — the 25.8% with no photo and the 67.9% with no rating are a ready-made pitch list.
Sibling Actors
| Actor | What it is | Why you would use it instead |
|---|---|---|
| Thumbtack Scraper | Top home-service pros by category + US city | Hires count, Top Pro badge and response time — signals Porch does not carry (no phone or e-mail on that surface) |
| Houzz Professionals Scraper | Houzz pros by trade and city | Design/remodel end of the market: budget band, Best of Houzz badges, social links |
| BBB Scraper | Better Business Bureau search results | When you want the BBB letter grade and accreditation status as the quality screen |
| California CSLB Scraper | All 243,555 licensed CA contractors, official registry | Licence data you can act on: current status, surety bond, workers' comp carrier |
| Florida DBPR Scraper | Licensed FL construction contractors, official records | Same, for Florida |
| HomeStars Scraper | Canadian contractor leads | Porch is US-only |
| Decision-Maker Email Finder | Domain → named contacts + validated e-mail | The other half of a Porch list: Porch gives you the website, this gives you the inbox |
Porch is where you go for phone, street address, geo and coverage at national scale. CSLB and DBPR are where you go for licence truth. Neither replaces the other.
FAQ
Does this need an account or login? No. No login, no cookie, no API key, no CAPTCHA solving service. Every page read is one Porch serves anonymously and lists in its own public sitemap.
Do I get e-mail addresses?
No — Porch publishes none, for anyone. 79.4% of pros have hasEmailOnFile: true, which only tells you
Porch holds one. Use the 65.2% of rows with a website and enrich from there.
Can I get the whole directory in one run?
Yes — select all 55 states (or all 131 trades) and set maxItems: 785653, about $2,749.79 at
$0.0035/row. Naming a scope is what unlocks it: a run that names no trade, state, city or profile
URL is treated as an unconfigured first click and runs the 50-row Texas-roofers sample instead, so
nobody buys a nationwide slice by accident. Most buyers slice by trade and state anyway, which is
free to do because those are read off the URL.
Do I get charged for rows my filters removed?
No. You are billed per pro delivered. The default run fetched 57 pages and delivered 50, and you pay
for 50. A minRating: "4" run fetches about 4.1 pages for every row you keep — that costs time and
the Actor's proxy budget, not your money.
Two runs — will I get duplicates?
Not from the sitemap: it holds 785,653 URLs with zero duplicates, verified by walking all 32 child
sitemaps end to end, so a run cannot return the same pro twice. Across runs, set skip to the number
of rows you already have; sitemap order is stable.
Is the address the pro's office or the area they serve?
street/city/state/zip is the registered business address. Coverage is a separate field —
serviceAreaZips, median 46 ZIPs and up to 872. Filtering by cities filters on the address.
Does it need a residential proxy? No, and that is a cost win. Porch is not bot-walled — it only rate-limits per IP. The default Apify Proxy (automatic, datacenter) measured 100% at concurrency 8, and the last three validation runs went 1,069 for 1,069 requests.
Why does my small run look nothing like the fill table? Because a filtered slice is richer than the national average, and because sitemap order is not shuffled. The prefilled TX-roofers run measured 92% website fill against a national 65.2%. Plan against the 399-row national table.
Are the licence records safe to rely on?
No — treat them as a lead. 24 of the 25 records in the sample had already expired, and 22 of those
still read ACTIVE in Porch's cache. Read the expired boolean and re-verify at authorityUrl.
How do I pull a specific list of pros I already know?
Paste their profile URLs into profileUrls. That skips the sitemap and is the fastest way to refresh
an existing list.
Can I schedule it?
Yes. Save your input as a Task, then attach an Apify Schedule (e.g. 0 6 * * 1 for Mondays at 06:00
UTC) and raise skip by your maxItems each week to walk the directory over time.
Will a run ever succeed with zero rows? No. A zero-row run throws with the full scope and HTTP counts. There is no quiet-empty mode.
Something looks wrong — how do I debug it? The run log prints the sitemap count, the candidate count, per-batch progress and a final measured fill line for that exact run. Compare it against the table above; if the fill collapsed, the run will usually have thrown already.
Legal & fair use
This Actor reads public Porch.com pro profile pages that Porch itself publishes and enumerates in its
own sitemap-pro-profile-index.xml. It does not log in, does not solve CAPTCHAs, does not use
browser automation, and collects nothing behind any authentication. It requests only URLs Porch lists
for crawlers.
Records describe businesses, but a sole trader's business phone and address can be personal data. You are responsible for complying with Porch.com's terms and with how you use the data — including CAN-SPAM, TCPA and state do-not-call rules for outreach, and CCPA/CPRA and GDPR where they apply to the people behind these listings.
Porch® is a trademark of its owner. This Actor is not affiliated with, endorsed by, or sponsored by Porch.com.
Feedback
Found a missing field, a trade that should be in the dropdown, or want a new filter? Open an issue on the Issues tab — it is read.