# Angi Scraper (`solidcode/angi-scraper`) Actor

\[💰 $0.8 / 1K] Extract contractor and home-service pro listings from Angi.com - business names, phone numbers, addresses, ratings, reviews, hours, and photos. Search by keyword and location, browse by category, paste Angi URLs directly. Optional per-pro reviews extraction.

- **URL**: https://apify.com/solidcode/angi-scraper.md
- **Developed by:** [SolidCode](https://apify.com/solidcode) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Stats:** 29 total users, 4 monthly users, 92.5% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Angi Scraper

Extract contractor and home-service pro listings from Angi.com at scale. Get business names, phone numbers, street addresses, ratings, review counts, opening hours, sub-categories, logos, descriptions, and per-pro reviews, for any trade in any US city or ZIP code. Built for lead-gen teams, agencies and market researchers who need structured data on local home-service pros without paying enterprise directory prices.

### Why This Scraper?

- **From $0.80 per 1,000 results** — one flat per-result rate for listings and full profiles alike, with no compute or time-based charges. Records Angi refuses to open are dropped before they reach your bill.
- **887 trade categories reachable** — a 30-trade dropdown covering `Plumbing`, `Heating & Cooling (HVAC)`, `Roofing`, `Junk Removal`, `Garage Doors`, `Concrete`, `Pools & Spas` and more, plus automatic mapping of any free-text term to Angi's nearest internal category.
- **Phone, street address and years in business on essentially every full profile** — plus a website on roughly 2 in 3 pros and 7-day opening hours on roughly 6 in 10, which is every pro who published them to Angi.
- **Multi-borough metros fan out automatically** — `"New York, NY"` sweeps Manhattan, Brooklyn, Queens, the Bronx and Staten Island, then de-duplicates pros by profile URL so a cross-borough repeat is never billed twice.
- **Two hard caps you can compute before you run** — `maxResults` is enforced across every search term, category, start URL and city combined, so a five-term run cannot quietly bill five times the number you typed; with reviews on, the worst case is exactly `maxResults x (1 + maxReviewsPerPro)`, printed in the log before collection starts, and `maxTotalRows` turns that into a single hard stop.
- **Two cost tiers in a single actor** — `Listing only` returns name, city, ZIP and profile URL for cheap seed lists; `Full profile` adds phone, hours, street address, sub-categories, logo and description.
- **Per-review rows on demand** — `Include Reviews` adds rating, full body text, reviewer name and posted date as their own dataset rows, capped per pro so the total stays predictable.
- **Spreadsheet-ready values** — city names arrive in one consistent spelling (Angi's own `austin` and `MESA` come out as `Austin` and `Mesa`), states as 2-letter codes, phones as 10-digit strings, so a pivot table groups each city into one bucket.
- **City or 5-digit ZIP, 50 states** — paste `78701` or `Austin, TX`; both resolve to the same Angi coverage.

### Use Cases

**Lead Generation**

- Build prospect lists of plumbers, electricians, roofers, or any trade in any city.
- Target newly-listed pros or those with low review counts for outreach.
- Pull phone and address data into a CRM for cold call campaigns.

**Market Research & Competitive Intelligence**

- Map the density of HVAC contractors across a metro area.
- Compare average ratings by trade between cities or ZIP codes.
- Track how many years pros in a category have been in business.

**Directory & Aggregator Sites**

- Populate a local-services directory with up-to-date pro listings.
- Enrich existing business records with Angi ratings and review counts.
- Build geographic coverage data for franchise expansion analysis.

**Reputation & Review Analysis**

- Pull per-pro reviews to analyze sentiment, common complaints, or pricing signals.
- Benchmark a single pro against the rest of their category in their city.
- Monitor competitor reviews on a recurring schedule via the Apify scheduler.

**Real Estate & Property Management**

- Build vendor lists for property managers in new markets.
- Identify highly-rated pros across multiple service categories in a single ZIP.

### Getting Started

#### Simplest — One trade, one city

```json
{
    "searchTerms": ["plumber"],
    "location": "Austin, TX"
}
```

#### Multi-trade in a major city by ZIP

```json
{
    "searchTerms": ["plumber", "electrician", "hvac"],
    "location": "78701",
    "maxResults": 50
}
```

#### Cheap bulk discovery (listing-only)

```json
{
    "category": "Roofing",
    "location": "Phoenix, AZ",
    "detailLevel": "listing",
    "maxResults": 50
}
```

#### With per-pro reviews

```json
{
    "searchTerms": ["roofing"],
    "location": "Phoenix, AZ",
    "includeReviews": true,
    "maxReviewsPerPro": 50
}
```

#### Direct URL — pinpoint a specific listing or category

```json
{
    "startUrls": [
        "https://www.angi.com/companylist/us/tx/austin/plumbing.htm"
    ],
    "maxResults": 100
}
```

#### Single pro profile (with reviews)

```json
{
    "startUrls": [
        "https://www.angi.com/companylist/us/tx/austin/we-plumb-reviews-10565886.htm"
    ],
    "includeReviews": true
}
```

### Input Reference

#### Search

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `searchTerms` | string\[] | `["plumber"]` | Keywords or service names (e.g. `"plumber"`, `"roof repair"`, `"HVAC contractor"`). Each term runs as its own search and is combined with the location. Mapped to Angi's nearest matching trade category. |
| `location` | string | `"Austin, TX"` | City and state (e.g. `"Austin, TX"`) or 5-digit US ZIP code (e.g. `"78701"`). Combined with each search term and category. |
| `category` | string | `null` | Optional trade to browse instead of (or alongside) free-text search terms. Pick from a dropdown of 30 common trades. |

##### Service categories available in the dropdown

`Plumbing`, `Electrical`, `Heating & Cooling (HVAC)`, `Roofing`, `Painting`, `Landscaping`, `Lawn Care`, `Tree Service`, `Handyman`, `House Cleaning`, `Pest Control`, `Remodeling`, `Kitchen Remodeling`, `Bathroom Remodeling`, `Flooring`, `Carpet Cleaning`, `Appliance Repair`, `Garage Doors`, `Fencing`, `Decks & Patios`, `Pools & Spas`, `Movers`, `Junk Removal`, `Windows`, `Siding`, `Gutters`, `Concrete`, `Masonry`, `Drywall`, `General Contractors`.

For trades outside this list, type the keyword into Search Terms — it will be auto-mapped to the closest of Angi's 887 internal categories.

#### Direct URLs

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `startUrls` | string\[] | `[]` | Paste any Angi URLs directly: pro profile pages, category hubs, city/state hubs, or listing pages. Scraped in addition to the keyword and category search above. |

#### Limits

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `maxResults` | integer | `100` | Total number of **pros** to return for the whole run, across every search term, category, start URL and city. A hard limit: you are never charged for more than this. Several searches share the total evenly, and unused allowance rolls on to the next search. Set to `0` for unlimited. Reviews are extra, so with reviews on your worst case is `maxResults x (1 + maxReviewsPerPro)` rows. |
| `maxTotalRows` | integer | `0` | Optional single hard stop on the **total** rows saved, pros and reviews together. `0` means no total cap. Set a number when you want one figure you can count on no matter how the pros and reviews divide up. The run says in its final message when this limit is what stopped it. |

#### Output Detail

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `detailLevel` | string | `"full"` | `"Listing only (faster, cheaper)"` returns just what's on the search result card. `"Full profile (recommended)"` also visits each pro's profile page for hours, photo gallery, full address, description, and the most complete contact info. |
| `keepPartialProfiles` | boolean | `false` | Once in a while Angi refuses to open a pro's profile page, so that record arrives with no phone, rating or hours. By default those records are dropped and never charged. Turn this on to keep them anyway — they come through with `detailFetchFailed: true`. Only applies under `Full profile`. |
| `includeReviews` | boolean | `false` | When on, fetches each pro's reviews and emits them as separate rows in the dataset (one row per review). Adds to your billed result count — a pro with 50 reviews counts as 51 results. |
| `maxReviewsPerPro` | integer | `20` | Cap on how many reviews to collect per pro. It multiplies: 100 pros at 25 reviews each is 2,500 review rows on top of the 100 pro rows. Set to `0` to fetch every review available, and pair that with `maxTotalRows` if you want a guaranteed ceiling. Ignored when `Include Reviews` is off. |

### Output

Two record types are emitted to the same dataset, distinguished by the `recordType` field. Pros always come first; reviews (when enabled) follow each pro.

#### Pro record (`recordType: "pro"`)

```json
{
    "recordType": "pro",
    "proId": "10565886",
    "name": "We Plumb",
    "url": "https://www.angi.com/companylist/us/tx/austin/we-plumb-reviews-10565886.htm",
    "category": "Plumbing",
    "subCategories": ["Plumbing", "Drain Cleaning", "Water Heaters"],
    "rating": 4.91,
    "reviewCount": 138,
    "phone": "5126581400",
    "email": null,
    "website": "https://weplumb.com/",
    "address": "641 Arrowood Place",
    "city": "Austin",
    "state": "TX",
    "zip": "78745",
    "hours": {
        "Monday": "7:00 AM - 6:00 PM",
        "Tuesday": "7:00 AM - 6:00 PM",
        "Wednesday": "7:00 AM - 6:00 PM",
        "Thursday": "7:00 AM - 6:00 PM",
        "Friday": "7:00 AM - 6:00 PM",
        "Saturday": "Closed",
        "Sunday": "Closed"
    },
    "acceptsAppointments": null,
    "yearsInBusiness": 12,
    "description": "Family-owned plumbing company serving Austin and surrounding communities since 2014. Drain cleaning, water heater install, repipe, slab leak repair.",
    "logoUrl": "https://media.angi.com/s3fs-public/...",
    "images": [],
    "detailFetchFailed": false,
    "searchTerm": "plumber",
    "searchLocation": "Austin, TX",
    "resolvedLocation": "Austin, TX",
    "scrapedAt": "2026-04-26T23:57:06.698355+00:00"
}
```

##### Core fields

| Field | Type | Description |
|-------|------|-------------|
| `recordType` | string | Always `"pro"`. |
| `proId` | string | Angi's own pro ID, unique to one business. Under `Listing only` Angi doesn't publish it, so the short number from the profile link is used instead and can repeat across unrelated businesses — `url` is always a guaranteed-unique key. |
| `name` | string | Business name. |
| `url` | string | Canonical Angi profile URL. |
| `category` | string | Primary service category (e.g. `"Plumbing"`). |
| `subCategories` | string\[] | Other services the pro offers (full profile only). Empty list under `Listing only`. |
| `rating` | number | null | Overall star rating, rounded to 2 decimals (0–5). May be `null` under `Listing only` for cards Angi doesn't ship a rating on. |
| `reviewCount` | integer | null | Total number of reviews on Angi. May be `null` under `Listing only` for cards without an aggregate rating. |

##### Contact

| Field | Type | Description |
|-------|------|-------------|
| `phone` | string | null | Public phone number, 10-digit US format with no punctuation (e.g. `"5126581400"`). Full profile only, and present on essentially every one. |
| `website` | string | null | The pro's own website or social page, exactly as published on their Angi profile. Roughly 2 in 3 pros list one; the rest come through as `null`. |
| `email` | string | null | Always `null`. Angi routes all contact through its own lead system and never publishes pro email addresses. |

##### Location

| Field | Type | Description |
|-------|------|-------------|
| `address` | string | null | Street address, including the suite or unit line when the pro published one (full profile only). Angi occasionally stores a placeholder label rather than an address, or a coverage area such as `"Las Vegas, NV"`; those come through as `null` instead of as junk text. |
| `city` | string | null | City, normalised to display casing so one city always groups into one bucket. |
| `state` | string | null | 2-letter state code, always upper-case. |
| `zip` | string | null | 5-digit ZIP code. |

##### Business detail

| Field | Type | Description |
|-------|------|-------------|
| `hours` | object | null | Per-day opening hours, keyed Monday–Sunday (full profile only). Roughly 6 in 10 pros publish them; the rest come through as `null`. |
| `yearsInBusiness` | integer | null | Self-reported tenure (full profile only). |
| `description` | string | null | Business description written by the pro (full profile only). |
| `logoUrl` | string | null | Logo image URL. |
| `acceptsAppointments` | boolean | null | Reserved field. Angi does not publish this on pro profiles, so it is always `null`. |
| `images` | string\[] | Reserved field. The photo gallery is not published on pro profiles, so this is always empty; the logo arrives in `logoUrl`. |

##### Run metadata

| Field | Type | Description |
|-------|------|-------------|
| `detailFetchFailed` | boolean | `true` only when the pro's profile page could not be opened, so the record is listing-level only. These records are dropped, and not charged, unless you turn on `Keep incomplete profiles`. |
| `searchTerm` | string | null | The keyword that surfaced this pro (when from a keyword search). |
| `searchLocation` | string | null | The place you asked for, echoed back (e.g. `"Tampa, FL"`), so you can join results straight back onto your own input list. A ZIP code comes back as the city it resolves to (`33139` → `"Miami Beach, FL"`). |
| `resolvedLocation` | string | null | The listing area Angi actually served. Usually identical to `searchLocation`; it differs when Angi files a city under its wider metro (`"Tampa, FL"` → `"Tampa Bay, FL"`) or splits it into boroughs (`"New York, NY"` → `"Manhattan, NY"`). |
| `scrapedAt` | string | ISO 8601 timestamp. |

#### Review record (`recordType: "review"`)

Emitted only when `includeReviews` is on. One row per review.

```json
{
    "recordType": "review",
    "proId": "10565886",
    "proName": "We Plumb",
    "proUrl": "https://www.angi.com/companylist/us/tx/austin/we-plumb-reviews-10565886.htm",
    "reviewId": "10565886-3",
    "rating": 5.0,
    "title": null,
    "text": "Showed up on time, fixed the leak in 30 minutes, and explained exactly what they did. Highly recommend.",
    "author": "Sarah M.",
    "authorLocation": null,
    "serviceDate": null,
    "postedAt": "2026-03-14T00:00:00",
    "serviceCategory": null,
    "cost": null,
    "scrapedAt": "2026-04-26T23:57:08.112441+00:00"
}
```

| Field | Type | Description |
|-------|------|-------------|
| `recordType` | string | Always `"review"`. |
| `proId` | string | Angi's own pro ID. Pair it with `proUrl` to link the review back to exactly one pro record. |
| `proName` | string | Convenience copy of the pro's business name. |
| `proUrl` | string | Convenience copy of the pro's profile URL. |
| `reviewId` | string | Stable per-scrape review ID, unique across the whole run (`{proId}-{globalIndex}`). |
| `rating` | number | null | Review star rating (0–5). |
| `title` | string | null | Review headline (currently `null` — not exposed by Angi's structured data). |
| `text` | string | null | Full review body. |
| `author` | string | null | Reviewer display name. |
| `authorLocation` | string | null | Reviewer city (currently `null` — not in structured data). |
| `serviceDate` | string | null | Date the work was performed (currently `null`). |
| `postedAt` | string | null | ISO 8601 timestamp of when the review was posted. |
| `serviceCategory` | string | null | Category the review is about (currently `null`). |
| `cost` | string | null | Reported job cost (currently `null`). |
| `scrapedAt` | string | ISO 8601 timestamp. |

### Tips for Best Results

- **Use a 5-digit ZIP for precision** — `"78701"` is more targeted than `"Austin, TX"` and avoids ambiguity for cities that share a name across states.
- **Major metros are auto-expanded** — searching `"New York, NY"` fans out across Manhattan, Brooklyn, Queens, the Bronx and Staten Island, then de-duplicates pros so you don't pay twice for cross-borough listings.
- **Pick `Listing only` for cheap bulk discovery** — it returns name, city, ZIP and profile URL from each card without opening any profile pages. Great for seed lists you'll enrich later. Switch to `Full profile` when you need phone, hours, sub-categories and the description.
- **Expect fewer unique pros than you asked for in smaller markets** — Angi repeats the same featured pros across listing pages, and the scraper de-duplicates them before saving. A `maxResults: 100` run on a typical city yields 30–60 unique pros; large metros give 50–150 per trade, small markets often 5–20. Raise `maxResults` or add a second nearby city when you need volume.
- **Reviews are billed per row** — turning on `Include Reviews` adds one billable result per review. A search returning 60 pros, each with 25 reviews kept, costs about 1,560 results (`60 x (1 + 25)`). Use `maxReviewsPerPro` to cap the per-pro count, or `maxTotalRows` to cap the whole run in one number.
- **Use Direct URLs for surgical scrapes** — paste a single profile URL to grab one pro and its reviews; paste a city or category URL (e.g. `https://www.angi.com/companylist/us/tx/austin/plumbing.htm`) to walk a known listing without going through the keyword mapper.
- **You only pay for complete records** — if Angi won't open a pro's profile page, that pro gets a second pass, and if it still won't load the record is dropped rather than saved half-empty. Flip on `Keep incomplete profiles` if a name plus city is still useful to you.
- **Search terms map to a category, so pick the dropdown when it matters** — Angi search is category-based, not free-text. `"exterminator"` maps to `Pest Control`, `"AC"` maps to `Heating & Cooling (HVAC)`. For full control over which category is used, choose from the dropdown or use Direct URLs.
- **A few fields are simply not published by Angi** — `email`, `acceptsAppointments` and the photo gallery are never exposed on pro profiles, and review rows carry no headline, reviewer city, service date, service category or job cost. They stay in the schema as `null` so your column mapping never shifts, and the dataset table hides them by default.

### Pricing

**From $0.80 per 1,000 results** — one flat rate whether you pull listing cards or full profiles. No compute or time-based charges: you pay per result, plus a small fixed start fee each time a run begins. Bronze, Silver and Gold subscribers pay progressively less, and the table below shows the total cost at each discount tier.

| Results | No discount | Bronze | Silver | Gold |
|---------|-------------|--------|--------|------|
| 100 | $0.096 | $0.091 | $0.085 | $0.080 |
| 1,000 | $0.96 | $0.91 | $0.85 | $0.80 |
| 10,000 | $9.60 | $9.10 | $8.50 | $8.00 |
| 100,000 | $96.00 | $91.00 | $85.00 | $80.00 |

A "result" is one row in your dataset. Each pro counts as one result; when `Include Reviews` is on, each review is also one result, so a pro with 50 reviews counts as 51. Standard Apify platform fees apply on top.

### Integrations

Export your data in JSON, CSV, Excel, XML, or RSS. Connect to 1,500+ apps via:

- **Zapier** / **Make** / **n8n** — Workflow automation.
- **Google Sheets** — Direct spreadsheet export.
- **Slack** / **Email** — Notifications on new results.
- **Webhooks** — Custom API integrations.
- **Apify API** — Full programmatic access.

### Legal & Ethical Use

This scraper is designed for legitimate business intelligence, market research, lead generation, and directory enrichment. You are responsible for complying with applicable laws, Angi's Terms of Service, and any data-protection regulations in your jurisdiction (CCPA, GDPR, etc.). Do not use the data for spam, harassment, or any unlawful purpose. Respect opt-outs and do-not-contact requests.

# Actor input Schema

## `searchTerms` (type: `array`):

Keywords or service names to search for on Angi (e.g. "plumber", "roof repair", "HVAC contractor"). Each term runs as its own search and is combined with the Location below. If you also pick a Service Category, that category is searched as well, in addition to your terms - each distinct trade is searched exactly once, so nothing is collected or charged twice. Terms are auto-mapped to Angi's nearest matching trade category (close-but-not-exact terms like "plumber repair" may be skipped if no category matches well - pick the Service Category dropdown for exact control). Leave empty if you only want to use a category browse or Start URLs.

## `location` (type: `string`):

City and state (e.g. "Austin, TX") or 5-digit US ZIP code (e.g. "78701"). Combined with each search term and with the category below. Leave empty if you only want to use Start URLs.

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

Optional Angi service category to browse. Use this when you want every pro in a single trade for your location, instead of a specific keyword. Combine with Location for a city-wide trade sweep. If you also enter Search Terms, this category is searched in addition to them (never instead of them), and any duplicate trade is searched only once.

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

Paste any Angi URLs directly: pro profile pages, category hubs, city or state hubs, or ZIP search pages (e.g. https://www.angi.com/companylist/us/tx/austin/plumbing.htm). When provided, these are scraped in addition to the keyword and category search above.

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

The total number of pros to save for the entire run - across every search term, every category and every start URL combined, and across every city a location expands into. This is a hard limit: the actor never saves, and you are never charged for, more pro records than this. When you enter several search terms or a location that covers several cities (New York, for example, is searched borough by borough), the total is shared out evenly between them, and whatever one search does not use is passed on to the next. Set to 0 for unlimited (every result Angi returns). This counts PROS ONLY - if you turn "Include Reviews" on, each pro also brings its own review rows on top, so your worst case becomes Maximum Pros x (1 + Max Reviews per Pro). Use "Maximum Rows in Total" below if you want a single hard number for the whole run. Note: small-to-medium markets may have fewer unique pros than the limit (Angi often serves only 30-60 unique pros for a single trade in a single city).

## `detailLevel` (type: `string`):

"Listing only" returns just what's shown on each search result card (cheap, fast). "Full profile" also visits each pro's profile page to get hours, photo gallery, full address, description, and the most complete contact info.

## `keepPartialProfiles` (type: `boolean`):

Now and then Angi refuses to open a pro's profile page, so that record comes back with no phone, rating or hours. By default those records are dropped, so you are never charged for a result that is missing the details you asked for. Turn this on if you would rather keep them anyway - they arrive with "detailFetchFailed" set to true so you can filter them out yourself. Only applies when Detail Level is "Full profile".

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

When on, fetches each pro's reviews and saves them as separate rows in the dataset (one row per review). Review rows are extra: they are NOT counted against "Maximum Pros", so a pro with 50 reviews adds 50 rows on top of that pro's own row. With reviews on, the most rows a run can save is Maximum Pros x (1 + Max Reviews per Pro). Set "Max Reviews per Pro" to keep that number where you want it, or set "Maximum Rows in Total" for a single hard stop.

## `maxReviewsPerPro` (type: `integer`):

Cap on how many reviews to collect for each pro. This is a hard limit: the actor never saves, and you are never charged for, more reviews than this for any one pro. It multiplies: every pro can add this many rows, so 100 pros at 25 reviews each is 2,500 review rows plus the 100 pro rows. Set to 0 to fetch every review a pro has (the total then depends on how many reviews Angi holds, so pair it with "Maximum Rows in Total" if you want a guaranteed ceiling). Ignored when "Include Reviews" is off.

## `maxTotalRows` (type: `integer`):

Optional hard stop on the TOTAL number of rows the run saves - pros and reviews added together. Leave at 0 for no total cap, which is the normal setting: the run then saves up to Maximum Pros x (1 + Max Reviews per Pro) rows. Set a number here when you want one figure you can rely on no matter how the pros and reviews divide up. The run stops saving the moment it reaches it, and tells you in the final message that it did, so a short result is never a mystery. Pros are always saved before that pro's reviews, so a capped run gives you as many complete businesses as it can fit.

## Actor input object example

```json
{
  "searchTerms": [
    "plumber"
  ],
  "location": "Austin, TX",
  "startUrls": [],
  "maxResults": 100,
  "detailLevel": "full",
  "keepPartialProfiles": false,
  "includeReviews": false,
  "maxReviewsPerPro": 20
}
```

# Actor output Schema

## `overview` (type: `string`):

Table of pros with key fields: name, category, rating, reviews, phone, location.

## `details` (type: `string`):

Full per-pro detail rows including hours, address, images, description, and contact info.

## `reviews` (type: `string`):

Per-review rows with rating, author, date, and text. Populated when "Include Reviews" is on.

# 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 = {
    "searchTerms": [
        "plumber"
    ],
    "location": "Austin, TX",
    "startUrls": [],
    "maxResults": 100,
    "detailLevel": "full",
    "keepPartialProfiles": false,
    "includeReviews": false,
    "maxReviewsPerPro": 20,
    "maxTotalRows": 0
};

// Run the Actor and wait for it to finish
const run = await client.actor("solidcode/angi-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 = {
    "searchTerms": ["plumber"],
    "location": "Austin, TX",
    "startUrls": [],
    "maxResults": 100,
    "detailLevel": "full",
    "keepPartialProfiles": False,
    "includeReviews": False,
    "maxReviewsPerPro": 20,
    "maxTotalRows": 0,
}

# Run the Actor and wait for it to finish
run = client.actor("solidcode/angi-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 '{
  "searchTerms": [
    "plumber"
  ],
  "location": "Austin, TX",
  "startUrls": [],
  "maxResults": 100,
  "detailLevel": "full",
  "keepPartialProfiles": false,
  "includeReviews": false,
  "maxReviewsPerPro": 20,
  "maxTotalRows": 0
}' |
apify call solidcode/angi-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,solidcode/angi-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/R01HadmgmJV93ycd5/builds/ff3K3lvVY0XwaEtaJ/openapi.json
