Angi Scraper — Pros, Reviews & Category Coverage — $3.5/1k
Under maintenancePricing
from $1.50 / 1,000 rows
Angi Scraper — Pros, Reviews & Category Coverage — $3.5/1k
Under maintenanceAngi (Angie's List) scraper for home service pros: ratings, reviews, phones, licenses, plus coverage mode that counts pros per city and service to expose market gaps. Listings or coverage rows. No login or cookies. MCP-ready for AI agents. $3.5 per 1,000 records.
Pricing
from $1.50 / 1,000 rows
Rating
0.0
(0)
Developer
Fortuitous Pirate
Maintained by CommunityActor stats
0
Bookmarked
40
Total users
3
Monthly active users
15 days ago
Last modified
Categories
Security
Limited permissionsShare
Angi Scraper - Contractors, Reviews & Category Coverage
Scrape Angi (formerly Angie's List) without a login, a browser or an API key. Two modes in one actor:
- Listings: one row per home-service pro for any city and service. Rating, review count, rating breakdown, badges, years in business, response time, address, logo, photos, and the featured review. Switch on
includeDetailsto open each pro's profile for phone number, license numbers, bonded and insured flags, review star distribution, project price range and the latest reviews. - Coverage: one row per city x service that answers "does Angi have this category in this city, and how many pros does it list?" It is a market-gap tool: find the services your city has supply for that Angi barely covers, or does not cover at all.
Plain HTTP, no Playwright, so runs are fast and cheap. Runs on the Apify platform with datacenter proxy first and an automatic residential fallback when Angi's Cloudflare blocks an exit IP.
What you get
Coverage rows (one per city x service)
| Field | Example | Meaning |
|---|---|---|
city, state, zip | San Diego, CA, null | Resolved location (ZIP codes are converted to their city) |
service | house cleaning | Your input, as typed |
categoryExists | true | Angi has a category page for this service in this city |
categoryStatus | ok | ok, no_pros, no_category, unknown_city or error (see below) |
categoryName | House Cleaning | Angi's display name for the category |
proCount | 961 | Total pros Angi lists for this city x service |
resultCount | 10 | Pros returned on page 1 |
sponsoredOnFirstPage | 10 | How many of the first-page pros are paid placements |
avgRating, ratingCount | 4.4, 496 | Average rating and rating count Angi publishes for the category page |
matchedSlug, triedSlugs | house-cleaning, ["house-cleaning"] | Which Angi category slug matched (up to 3 spellings are tried) |
pageUrl | https://www.angi.com/companylist/us/ca/san-diego/house-cleaning.htm | The Angi page the numbers came from |
topPros | ["WHB Cleaning", ...] | Names of the first 3 pros |
Listing rows (one per pro)
| Group | Fields |
|---|---|
| Identity | name, providerId, providerUuid, profileUrl (also url), logoUrl, photos, photosCount |
| Reputation | rating, reviewCount, recommendedRate, ratingBreakdown (overall, quality, value, responsiveness, punctuality, professionalism where Angi shows them), featuredReview, featuredReviewDate |
| Badges | angiApproved, sponsored, superServiceAward, corporateAccount, unlicensedContractor, badges, awards |
| Business | yearsInBusiness, inBusinessSince, introduction, services / categories, servingArea, street, city, state, postalCode |
| Offer flags | freeEstimates, emergencyServices, warrantiesOffered, veteranOwned, bilingual, smallJobsWelcome, offersCommercialServices, paymentMethods, contactMethods |
| Responsiveness | responseTime, responseTimeMinutes, responseRate, responseRatePercent, recentQuoteRequests (neighbors who recently requested a quote) |
| Search context | searchCity, searchState, searchService, matchedSlug, searchPage, position, scrapedAt |
With includeDetails (one extra profile page per pro) the row also gets:
| Field | Example |
|---|---|
phone, phoneRaw | (747) 256-4174, 7472564174 |
licenses | [{"number":"1110710","state":"CA","expires":"09/30/2027"}], plus licensed true/false |
bonded, insured | false, false |
ecoFriendlyAccreditations | [] |
servicesOffered | Free-text "Services we offer" from the profile |
reviewDistribution | {"1star":1,"2star":0,"3star":0,"4star":1,"5star":83} |
priceFrom, priceRange | From the min and max project cost of the pro's photo albums, when present |
albumsCount, highlights, categoryIds, reviewPages | Profile metadata |
recentReviews | With maxReviewsPerPro: reviewer, rating, date, text, job, cost, verified, proResponse |
What Angi does not publish
Said plainly so you do not buy something you will not get:
- Website URLs are not on Angi's public pages, so there is no
websitefield. - Business hours are empty in the data Angi serves for every pro we sampled, so there are no hours.
- Letter grades (A-F) are not in the public pages. You get the numeric rating breakdown and the star distribution instead.
- Reviews stop at 25 per pro.
maxReviewsPerProis capped at 25, the latest page on the profile. - Angi has no ZIP-level pages. A ZIP code is resolved to its city and the city page is scraped.
- Angi only has home-service categories (314 of them). It has no cake, dog grooming or other non-home categories, which is exactly what coverage mode will tell you.
Coverage mode: find the gaps
Coverage mode costs one page request per city x service and returns a single row per pair, even when nothing matches, so a gap is always visible in the output.
categoryStatus | categoryExists | Meaning |
|---|---|---|
ok | true | Category page exists and lists pros. proCount is Angi's number. |
no_pros | true | The category exists in that city but lists 0 pros (example: pool and spa service in Nome, AK). |
no_category | false | None of the tried slugs exist on Angi, for example a service Angi does not cover at all. |
unknown_city | false | Angi has no page for that city under the spelling given. Check the spelling or try a nearby city. |
error | null | The request failed (blocked, timeout). The row is kept so the grid stays complete, and it is not charged. |
A typical workflow: take the services your local directory, Yelp export or Google Maps scrape says exist in your city, run them through coverage mode for the same city, and sort by proCount. Services with no_category, no_pros or a proCount in single digits are places where a marketplace offers customers little, and where a local provider, lead-gen site or agency has room.
Real coverage rows, from the default {} run (San Diego x cake, dog grooming, house cleaning):
{"recordType": "coverage","marketplace": "angi","city": "San Diego","state": "CA","zip": null,"service": "house cleaning","categoryExists": true,"categoryStatus": "ok","categoryName": "House Cleaning","proCount": 961,"resultCount": 10,"sponsoredOnFirstPage": 10,"avgRating": 4.4,"ratingCount": 496,"pageUrl": "https://www.angi.com/companylist/us/ca/san-diego/house-cleaning.htm","matchedSlug": "house-cleaning","triedSlugs": ["house-cleaning"],"topPros": ["At Your Service Construction Clean and Home Detail - Unlicensed Contractor","WHB Cleaning","Del Sol Cleaning Services"],"scrapedAt": "2026-10-22T02:09:54.241Z"}
{"recordType": "coverage","marketplace": "angi","city": "San Diego","state": "CA","zip": null,"service": "cake","categoryExists": false,"categoryStatus": "no_category","categoryName": null,"proCount": null,"resultCount": 0,"sponsoredOnFirstPage": 0,"avgRating": null,"ratingCount": null,"pageUrl": null,"matchedSlug": null,"triedSlugs": ["cake"],"topPros": [],"scrapedAt": "2026-10-22T02:09:54.241Z"}
Input
| Input | Type | Default | Meaning |
|---|---|---|---|
mode | listings / coverage | see below | Row per pro, or row per city x service |
cities | string[] | - | "San Diego, CA", "Austin, Texas", "Austin TX" or a ZIP code |
services | string[] | - | Plain words ("plumbers", "pool cleaning", "junk removal") or an Angi slug ("pest-control"). Fuzzy-matched over 314 Angi category slugs; up to 3 slugs are tried |
zipCodes | string[] | - | ZIP codes, converted to their city |
category | string | - | One Angi slug; same as adding it to services |
startUrls | URL list | - | Angi category pages or pro profile pages |
listUrl | string | - | Public CSV or Google Sheet; each row a city, a city plus service, or an Angi URL |
maxItems | integer | 10 | Max pros per city x service (Angi shows 10 per page) |
includeDetails | boolean | false | Open each pro's profile for phone, licenses and more |
maxReviewsPerPro | integer | 0 | Latest reviews per pro (0-25), used with includeDetails or profile URLs |
concurrency | integer | 3 | Parallel searches |
residentialFallback | boolean | true | Retry blocked requests through Apify RESIDENTIAL (US) |
proxyConfiguration | proxy | Apify datacenter | First proxy tier tried |
state, city, service | string | - | Legacy single search (state code, city slug, service slug). Still works |
maxResults | integer | - | Legacy alias of maxItems |
Which mode runs? If you set mode, that one. With no input at all the actor runs a coverage sample (San Diego x cake, dog grooming, house cleaning) so the first test run returns data in seconds. With cities/services, startUrls or the legacy fields and no mode, it runs listings.
Example 1: coverage grid for a city
{"mode": "coverage","cities": ["San Diego, CA"],"services": ["house cleaning", "dog grooming", "roofers", "pool cleaning", "wedding cake"]}
Five rows, one per service.
Example 2: coverage across many cities from a sheet
{"mode": "coverage","listUrl": "https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/export?format=csv"}
Each sheet row is "city, state, service" (for example Nome,AK,pool cleaning) or just a city to be combined with the services input.
Example 3: roofers with phone numbers and reviews
{"mode": "listings","cities": ["Austin, TX", "92109"],"services": ["roofers"],"maxItems": 5,"includeDetails": true,"maxReviewsPerPro": 2}
Up to 5 pros per city, each with phone, licenses, review distribution and the 2 latest reviews. The ZIP 92109 resolves to San Diego.
Example 4: specific pages
{"startUrls": [{ "url": "https://www.angi.com/companylist/us/ca/julian/chimney-sweep.htm" },{ "url": "https://www.angi.com/companylist/us/ca/chula-vista/primo-plumbing-reviews-1.htm" }],"maxReviewsPerPro": 5}
A category page returns its listings; a profile URL returns that pro's profile row with details and reviews.
Example 5: legacy single search
{ "state": "ca", "city": "san-francisco", "service": "plumbing", "maxResults": 12 }
Existing saved inputs keep working and return listing rows with the same field names as before, plus the new ones.
Real listing row
From a cloud run with includeDetails (long arrays shortened here for readability only):
{"recordType": "listing","marketplace": "angi","name": "Clear View Roofing & Construction","rating": 4.89,"reviewCount": 115,"recommendedRate": 50,"ratingBreakdown": { "overall": 4.94, "responsiveness": 5, "quality": 5, "value": 5 },"services": ["Asphalt Shingle Roofing - Install or Replace", "Asphalt Shingle Roofing - Repair", "... (27 total)"],"servingArea": "Serving San Diego, CA and surrounding areas","angiApproved": true,"sponsored": true,"superServiceAward": false,"unlicensedContractor": false,"badges": ["Angi Approved", "Sponsored"],"yearsInBusiness": 36,"inBusinessSince": 1990,"freeEstimates": true,"emergencyServices": true,"warrantiesOffered": true,"bilingual": true,"paymentMethods": ["CREDIT_CARD"],"responseTime": "7 hrs","responseTimeMinutes": 385,"responseRate": "100%","recentQuoteRequests": 564,"contactMethods": ["CALL_DIRECT", "CALL_REQUEST", "MESSAGE", "CONTACT_NOW"],"street": "12520 Kirkham Ct Suite 2","city": "Poway","state": "CA","postalCode": "92064","logoUrl": "https://cdn.homeadvisor.com/files/eid/141780000/141784072/20617544_logo.png","photosCount": 2,"phone": "(747) 256-4174","providerId": "141784072","profileUrl": "https://www.angi.com/companylist/us/ca/san-diego/clear-view-roofing-construction-reviews-1.htm","searchCity": "San Diego","searchState": "CA","searchService": "roofers","matchedSlug": "roofing","position": 5,"licenses": [{ "number": "1110710", "state": "CA", "authority": null, "status": null, "expires": "09/30/2027" }],"licensed": true,"bonded": false,"insured": false,"reviewDistribution": { "1star": 1, "2star": 0, "3star": 0, "4star": 1, "5star": 83 },"albumsCount": 2,"recentReviews": [{"reviewer": "anna S.","rating": 5,"date": "October 2026","text": "I couldn't be happier with how my tile roof turned out! ...","job": "Traditional Tile Roofing - Install or Replace","cost": "$18,000","verified": true,"proResponse": null}],"scrapedAt": "2026-10-22T02:10:20.101Z"}
Note that licensed: true means a license number is on the profile. insured and bonded reflect what the pro has published on Angi, so false means "not shown", not "proven uninsured".
Use cases
- Local market gap analysis. Compare your city's real service supply (Yelp, Google Maps) against what Angi covers. Missing or single-digit categories are openings for a local directory, a lead-gen site or a new service line.
- Contractor lead lists. Pull pros by trade and city with phone, license number, years in business and rating, filter by rating and review count, and load the result into a CRM.
- Competitor monitoring for a service business. Track how many pros, how many sponsored placements and what average rating Angi shows for your trade in your city, week over week, with a schedule.
- Reputation benchmarking. Compare your rating, review count, response time and response rate against the pros that rank above you in your city.
- Sales targeting for software and insurance vendors. Find unlicensed, uninsured or unbonded contractors, or high-volume pros that get hundreds of quote requests.
- Market sizing for franchises and roll-ups.
proCountper city x service gives a quick count of how many pros compete in a category, across a whole list of cities. - Review research. Latest reviews with job type, cost and verified flag, for sentiment or price analysis.
Pricing
Pay per event. You pay the start fee plus the rows that are saved to the dataset. Both modes bill the same per-row price, and coverage rows are rows.
| Until 5 Nov 2026 | From 6 Nov 2026 | |
|---|---|---|
| Start fee (per run, 512 MB) | $0.005 | $0.001 |
| Per row (listing or coverage) | $0.0035 | $0.0015 |
| Cost of 1,000 rows | $3.50 | $1.50 |
profile-details add-on, per pro whose profile is opened with includeDetails | - | $0.002 |
| Cost of 1,000 pros with details (phone, licenses, reviews) | - | $3.50 |
Coverage rows that fail (categoryStatus: "error") are saved so your grid stays complete, but they are not charged. Use maxItems and the Apify "max cost per run" setting to cap spend.
At the 6 Nov 2026 prices: a coverage grid of 50 cities x 20 services is 1,000 rows, $1.50 plus the start fee. 100 roofers with phone numbers is 100 rows plus 100 detail events, $0.35.
How it works and limits
- Angi serves its category and profile pages through server-rendered Next.js data. The actor reads that embedded data directly, so there is no browser and no page rendering, and a run uses about 0.8 MB per search page and 0.4 MB per profile.
- Angi sits behind Cloudflare. The actor sends requests with a browser-like TLS fingerprint, tries the proxy you configure (Apify datacenter by default), and if a request is blocked, retries without proxy and then through Apify RESIDENTIAL (US). Which exit IPs get through varies, so residential fallback is on by default. Switch it off with
residentialFallback: falseif you want to avoid residential traffic costs on your Apify bill. - Requests are throttled so one exit IP is not rate-limited. A very large grid is better split across several runs or run with
concurrencyat the default. - Partial results are kept. If part of a run is blocked, finished rows stay in the dataset and the
OUTPUTrecord in the key-value store holds a per-search summary (status, rows, failures, proxy tier used). - City spelling matters. Angi uses
st-louisandmanhattan, not "Saint Louis" or "New York" in some places. If you getunknown_city, try a nearby spelling or a neighboring city. - Angi shows 10 pros per page;
maxItemsabove 10 pages through results and stops at the last page.
FAQ
Is this the official Angi API? No. It reads Angi's public web pages. No login, cookies or account are used or needed.
Why is website missing? Angi's public profile pages do not show a business website. The phone number is what Angi displays, and it can differ from a number written in the pro's own description.
Why does a service return no_category? Angi has 314 home-service categories. A service outside them (cake, dog grooming) has no page. That result is the answer coverage mode exists to give.
Does proCount equal the number of pros I can scrape? proCount is the total Angi reports for the city x category. Listings mode returns up to maxItems of them, 10 per page.
Can I get all of a pro's reviews? Only the latest 25 per pro.
Can I search by ZIP? Yes, as a city: the ZIP is converted to its city and that city's page is scraped.
Will my old saved input still work? Yes. state, city, service and maxResults are kept, and the original listing field names (name, rating, reviewCount, services, servingArea, angiApproved, inBusinessSince, freeEstimates, emergencyServices, responseTime, responseRate, featuredReview, providerId, url) are unchanged. responseTime is now formatted from minutes (for example "3 hrs", "1 day").
Integrations
Run it from the Apify Console, on a schedule, or through the API.
API (curl)
curl -s -X POST "https://api.apify.com/v2/acts/fortuitous_pirate~angi-home-services-contractors-reviews/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"mode":"coverage","cities":["San Diego, CA"],"services":["plumbing","dog grooming"]}'
Python
from apify_client import ApifyClientclient = ApifyClient("APIFY_TOKEN")run = client.actor("fortuitous_pirate/angi-home-services-contractors-reviews").call(run_input={"mode": "listings","cities": ["Austin, TX"],"services": ["plumbers"],"maxItems": 20,"includeDetails": True,})for pro in client.dataset(run["defaultDatasetId"]).iterate_items():print(pro["name"], pro["rating"], pro.get("phone"))
JavaScript
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('fortuitous_pirate/angi-home-services-contractors-reviews').call({mode: 'coverage',cities: ['San Diego, CA', 'Austin, TX'],services: ['house cleaning', 'roofers'],});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items.map(r => [r.city, r.service, r.categoryStatus, r.proCount]));
- Schedules: run a coverage grid weekly and compare
proCountandsponsoredOnFirstPageover time. - Webhooks: call your endpoint when a run succeeds.
- Make, Zapier, n8n: use the Apify app or HTTP module with the run-sync call above, then send rows to Google Sheets, Airtable or a CRM.
- Exports: JSON, CSV, Excel, XML or HTML from the dataset.
- MCP / AI agents: the actor is available through the Apify MCP server, so an AI agent can call it with the same input as above.
Support
Open an issue on the actor's Issues tab with the input you used and the run link.