# Angi Home Services Scraper (`khadinakbar/angi-home-services-scraper`) Actor

Scrape Angi (Angie's List) home-service pros by city, ZIP, category URL, or profile — name, phone, rating, reviews, address, website. Cookieless; residential proxy required. MCP-ready.

- **URL**: https://apify.com/khadinakbar/angi-home-services-scraper.md
- **Developed by:** [Khadin Akbar](https://apify.com/khadinakbar) (community)
- **Categories:** Lead generation, Other, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.00 / 1,000 provider enricheds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Angi Home Services Scraper

Scrape **public Angi (Angie's List)** home-service providers by category + city/state, ZIP, companylist URL, or profile URL. Each dataset row is one contractor with **name, phone, rating, review count, address, website, categories, badges, and Angi profile URL** from public guest pages — cookieless directory access. Run it from Apify Console, the Apify API, or Apify MCP with Apify **RESIDENTIAL US** proxy (required for Angi Cloudflare).

Built for B2B contractor shortlists, local-market research, and CRM enrichment when your workflow starts from **Angi directories** (plumbing, HVAC, electrical, roofing, and other published trades). Phone numbers are included when Angi publishes them on the guest profile; some merchants keep phone gated.

### Best fit

Use this Actor when you need Angi directory providers for a trade in a US city, or you already have `angi.com` companylist/profile URLs and want a flat, billable provider dataset for MCP, CRM, or spreadsheet workflows.

When you need Google Maps NAP or Yelp business directories instead, continue with [Local Business Lead Generator — Google Maps](https://apify.com/khadinakbar/google-maps-leads-scraper) or [Yelp Scraper All-in-One](https://apify.com/khadinakbar/yelp-scraper-all-in-one). Keep Angi URLs and trade+city directory jobs on this Actor.

### Practical scenario

A home-services marketplace lead needs plumbers in Austin, TX for an outreach shortlist. They run the Console prefill (`plumbing`, `austin`, `tx`, `maxResults: 3`, `enrichDetails: true`). The dataset returns three Angi providers with rating, review count, profile URL, and enriched public fields when available. On a second run they set `enrichDetails: false` for a cheaper listing-card scan, then enrich only the shortlisted profiles with direct profile `startUrls`.

### Quick start input

```json
{
  "category": "plumbing",
  "city": "austin",
  "state": "tx",
  "maxResults": 10,
  "enrichDetails": true
}
```

### Input reference

| Field | Type | What it controls |
| --- | --- | --- |
| `category` | string | Angi trade slug (prefill: plumbing) |
| `city` | string | City slug or name (prefill: austin) |
| `state` | string | Two-letter US state (prefill: tx) |
| `zipCodes` | array | Optional US ZIPs resolved to city/state when category is set |
| `startUrls` | array | Angi companylist or profile URLs |
| `maxResults` | integer | Cap 1–2000 (default 100, prefill 3) |
| `enrichDetails` | boolean | Profile enrich vs listing cards (default true) |
| `includeReviews` | boolean | Attach bounded review previews (default false) |
| `maxReviewsPerProvider` | integer | Review preview cap when includeReviews is true |
| `minRating` | number | Keep providers at or above this star rating |
| `requirePhone` / `requireWebsite` | boolean | Keep only rows with phone/website |
| `maxPagesPerListing` | integer | Listing pagination bound |
| `proxyConfiguration` | object | Use Apify RESIDENTIAL US |

Empty input with no category/city/state, ZIP+category, or Angi URLs finishes `INVALID_INPUT` with zero provider charges.

### What data you receive

One dataset item is one Angi provider. Download the dataset as JSON, HTML, CSV, or Excel.

| Field | Meaning |
| --- | --- |
| `businessName` | Public Angi display name |
| `angiUrl` | Canonical profile URL |
| `phone` | Public phone when Angi publishes it |
| `website` | External website when published |
| `rating` / `grade` / `reviewCount` | Public reputation signals |
| `categories` / `services` | Trade categories |
| `street` / `city` / `state` / `postalCode` | Address when published |
| `badges` | Super Service Award, certified, and similar public badges |
| `reviews` | Optional bounded review previews |
| `scrapedAt` | ISO-8601 collection time |

```json
{
  "businessName": "Example Plumbing Co",
  "angiUrl": "https://www.angi.com/business/example-plumbing/12345",
  "phone": "(512) 555-0100",
  "website": "https://example-plumbing.example",
  "rating": 4.8,
  "reviewCount": 42,
  "city": "Austin",
  "state": "TX",
  "categories": ["Plumbing"],
  "scrapedAt": "2026-09-17T12:00:00.000Z"
}
```

`OUTPUT` and `RUN_SUMMARY` in the default key-value store hold `outcome`, `itemsPushed`, and `chargedEventCounts`.

### Pricing

This Actor uses **Pay per event** plus **platform usage**. Event prices below are the launch defaults; treat the live Pricing tab on the Actor page as the current source of truth, because platform usage (compute and residential proxy) is billed to the caller in addition to named events.

| Event | Price |
| --- | --- |
| `apify-actor-start` | $0.00005 |
| `provider-found` (listing-only) | $0.005 per saved listing card |
| `provider-enriched` (primary) | $0.008 per enriched provider |

Invalid input, empty matches, and total Cloudflare challenges skip the named provider events. Worked example: `maxResults: 10` with enrich on ≈ `$0.00005` start + `10 × $0.008` = about `$0.08005` in named events, plus platform usage.

### Best results

Start with `category` + `city` + `state` and `maxResults` at 3–20 while validating a new metro. Prefer city+state over ZIP-only or state-only hubs — Angi companylist city pages are the reliable route. Keep `enrichDetails: true` when you need phone, address, website, and badges; set it false for cheaper directory scans. Turn on `requirePhone` only when your CRM needs dialable rows (some guest profiles omit phone). Use direct profile `startUrls` for refresh jobs when you already have Angi links.

### How it works

The Actor uses Cheerio over HTTP with Apify RESIDENTIAL US (Chrome TLS) to clear Angi Cloudflare, parses public JSON-LD ItemList / LocalBusiness payloads, optionally opens each profile for enrich, dedupes by Angi URL, and writes honest `OUTPUT` / `RUN_SUMMARY` outcomes (`COMPLETE`, `PARTIAL`, `VALID_EMPTY`, `INVALID_INPUT`, `UPSTREAM_FAILED`).

### Use through the API

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });

const run = await client.actor('khadinakbar/angi-home-services-scraper').call({
  category: 'plumbing',
  city: 'austin',
  state: 'tx',
  maxResults: 10,
  enrichDetails: true,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Use with AI agents through Apify MCP

> Find 10 Angi plumbers in Austin, TX. Return businessName, phone, rating, reviewCount, angiUrl, city, state, and scrapedAt. Read the dataset plus OUTPUT for outcome and provenance, then confirm cost on the Pricing tab.

Connect via <https://mcp.apify.com>. Tool name: `apify--angi-home-services-scraper`. Cost signal: about `$0.008` per enriched provider (or `$0.005` listing-only) plus platform usage. Confirm current event prices on the Actor Pricing tab before large runs. Scope stays public Angi guest directory fields; private sessions and lead-marketplace actions belong on other workflows.

### Outcomes

| Outcome | Meaning |
| --- | --- |
| `COMPLETE` | At least one provider saved. |
| `PARTIAL` | Some providers saved; others empty or hit a charge/request stop. |
| `VALID_EMPTY` | Input resolved with zero matching public providers. |
| `INVALID_INPUT` | No usable category/city/state, ZIP+category, or Angi URLs. |
| `UPSTREAM_FAILED` | Cloudflare or transport returned no useful pages. |

### Legal & responsible use

Use this Actor for **public** Angi directory and profile data you are authorized to process under applicable laws and site terms. Respect privacy rules (including GDPR/CCPA where applicable) and marketing contact rules for outreach. Keep use cases to legitimate research, lead generation, and analysis. Angi and Angie's List are trademarks of Angi Inc. This independent Actor is not affiliated with, associated with, or endorsed by Angi Inc.

### Builder's note

I found that bare HTTP against angi.com returns Cloudflare 403, while Cheerio + Apify RESIDENTIAL US clears the challenge with a lean HTTP path. Current listing and profile pages expose structured data as JSON-LD ItemList / LocalBusiness, so the parser follows that path after live probes. State-only companylist hubs and phone-gated guest profiles finish as honest `VALID_EMPTY` when filters leave zero rows, so billing stays tied to persisted providers.

# Changelog

This Actor's version history is a separate document: https://apify.com/khadinakbar/angi-home-services-scraper/changelog.md

# Actor input Schema

## `category` (type: `string`):

Angi trade slug used in companylist URLs, for example plumbing, electrical, hvac, or roofing. Pair with city and state, or with zipCodes. Ignored when startUrls alone drive the run. NOT a free-text Google query.

## `city` (type: `string`):

US city for the Angi directory page, for example austin or New York. Requires state. Prefer this over ZIP for accurate companylist URLs. Ignored when only profile startUrls are supplied.

## `state` (type: `string`):

Two-letter US state code, for example tx, ca, or ny. Required with city. Case-insensitive.

## `zipCodes` (type: `array`):

Optional US 5-digit ZIPs. Each ZIP is resolved to a city/state listing URL when category is set. Angi ZIP query params are often non-filtering — city slug remains authoritative.

## `startUrls` (type: `array`):

Direct angi.com companylist pages and/or business profile URLs (/business/..., companyreviews.htm?spid=, or \*-reviews-{id}.htm). Overrides structured search when present. Non-angi URLs are ignored.

## `maxResults` (type: `integer`):

Cap on unique billed provider rows for the run. Default 100. Prefill 3 keeps quality checks fast and cheap. You are billed per persisted provider.

## `enrichDetails` (type: `boolean`):

When true (default), open each listing profile for phone, address, website, services, and badges. When false, return lighter listing cards only (cheaper provider-found event). Direct profile startUrls are always enriched.

## `includeReviews` (type: `boolean`):

Attach up to maxReviewsPerProvider public review excerpts when Angi exposes them. Not a full review archive. Default false.

## `maxReviewsPerProvider` (type: `integer`):

Maximum review excerpts to attach when includeReviews is true. Default 5.

## `minRating` (type: `number`):

Skip providers below this average star rating (0–5). Default 0 (no filter). Providers with no published rating are excluded when a minimum is set.

## `requirePhone` (type: `boolean`):

Skip profiles with no public phone number. Does not unlock Angi-gated numbers. Default false.

## `maxPagesPerListing` (type: `integer`):

Maximum companylist pages to paginate per seed URL. Default 5.

## `proxyConfiguration` (type: `object`):

Apify RESIDENTIAL US is required. Datacenter proxies are Cloudflare-blocked on angi.com. Override only if you know what you are doing.

## `storeRawSample` (type: `boolean`):

Developer/debug option. Saves the first listing and profile HTML plus **NEXT\_DATA** to the key-value store. Leave off for normal runs.

## Actor input object example

```json
{
  "category": "hvac",
  "city": "denver",
  "state": "co",
  "zipCodes": [
    "78701"
  ],
  "startUrls": [
    "https://www.angi.com/companylist/us/tx/austin/plumbing.htm"
  ],
  "maxResults": 3,
  "enrichDetails": true,
  "includeReviews": false,
  "maxReviewsPerProvider": 5,
  "minRating": 0,
  "requirePhone": false,
  "maxPagesPerListing": 5,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "storeRawSample": false
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `OUTPUT` (type: `string`):

No description

## `RUN_SUMMARY` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "category": "plumbing",
    "city": "austin",
    "state": "tx",
    "zipCodes": [
        "78701"
    ],
    "startUrls": [
        "https://www.angi.com/companylist/us/tx/austin/plumbing.htm"
    ],
    "maxResults": 3,
    "enrichDetails": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("khadinakbar/angi-home-services-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "category": "plumbing",
    "city": "austin",
    "state": "tx",
    "zipCodes": ["78701"],
    "startUrls": ["https://www.angi.com/companylist/us/tx/austin/plumbing.htm"],
    "maxResults": 3,
    "enrichDetails": True,
}

# Run the Actor and wait for it to finish
run = client.actor("khadinakbar/angi-home-services-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "category": "plumbing",
  "city": "austin",
  "state": "tx",
  "zipCodes": [
    "78701"
  ],
  "startUrls": [
    "https://www.angi.com/companylist/us/tx/austin/plumbing.htm"
  ],
  "maxResults": 3,
  "enrichDetails": true
}' |
apify call khadinakbar/angi-home-services-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,khadinakbar/angi-home-services-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Q8bDbhf70u3tFVEkz/builds/ffE4NQEZNYBEKOZRO/openapi.json
