# BBB Business Scraper — Ratings & Complaints (`muhammadafzal/bbb-business-scraper`) Actor

Extract BBB business profiles, ratings, accreditation, contacts, reviews, complaints, categories, and trust signals from public BBB.org pages.

- **URL**: https://apify.com/muhammadafzal/bbb-business-scraper.md
- **Developed by:** [Muhammad Afzal](https://apify.com/muhammadafzal) (community)
- **Categories:** Lead generation, Automation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 bbb profile scrapeds

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/platform/actors/running/actors-in-store#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

## BBB Business Scraper — Ratings, Accreditation & Complaints

Extract public **Better Business Bureau (BBB)** business profiles from BBB.org into clean, structured JSON. The actor supports keyword and location searches, direct BBB search URLs, and individual BBB profile URLs. It returns business identity, contact details, address, categories, BBB rating, accreditation status, years in business, review and complaint counts, and optional public review or complaint snippets.

This is designed for B2B lead generation, vendor due diligence, contractor research, business-reputation monitoring, market intelligence, and AI-agent workflows. Every dataset row has the same field shape and includes `profile_url`, `source_url`, and `scraped_at` for traceability.

### What it extracts

- Business name, canonical BBB profile URL, country, address, phone, and public email
- Public business website and additional websites listed on the profile
- BBB letter rating and accreditation status
- Years in business, BBB file opened date, business started date, entity type, and local BBB office
- BBB business categories and products/services
- Customer review rating and count when exposed by the public profile
- Complaint count when exposed by the public profile
- Optional visible review snippets and complaint summaries
- Public management contacts, payment methods, and operating hours when available

BBB content changes by profile, country, local BBB office, and the current public page layout. Missing values are returned as `null`; optional collections are returned as empty arrays. The actor does not invent contact information or infer a rating when BBB does not display one.

### Input modes

#### Search mode

Use `searchQuery` and optionally `location`:

```json
{
  "searchQuery": "roofing contractor",
  "location": "Austin, TX",
  "country": "US",
  "maxResults": 25
}
```

The actor builds BBB directory search URLs and visits up to `maxPagesPerSearch` pages. If `location` is blank, BBB receives a nationwide/category search.

#### Direct URL mode

Use `startUrls` for BBB search pages or profile pages:

```json
{
  "startUrls": [
    { "url": "https://www.bbb.org/us/az/phoenix/profile/plumber/quail-plumbing-inc-1126-26001867" }
  ],
  "includeReviews": true,
  "includeComplaints": true,
  "maxResults": 10
}
```

Search URLs discover profile links. Profile URLs are parsed directly. Search and profile URLs can be mixed in one run.

### Input reference

| Field | Default | Description |
|---|---:|---|
| `searchQuery` | `plumber` | BBB business name, category, keyword, phone, website, or email search. |
| `location` | `Phoenix, AZ` | City, state, ZIP, or postal code. |
| `country` | `US` | `US` or `CA`. |
| `startUrls` | `[]` | BBB search or profile URLs. |
| `maxResults` | `10` | Maximum profile records returned and charged. |
| `maxPagesPerSearch` | `3` | Search pages visited per generated or supplied search URL. |
| `includeReviews` | `false` | Parse visible public review snippets. |
| `includeComplaints` | `false` | Visit the public complaints tab and parse visible summaries. |
| `proxyConfiguration` | Residential | Optional Crawlee proxy configuration. |

### Output example

```json
{
  "business_name": "Quail Plumbing Inc",
  "profile_url": "https://www.bbb.org/us/az/phoenix/profile/plumber/quail-plumbing-inc-1126-26001867",
  "source_url": "https://www.bbb.org/us/az/phoenix/profile/plumber/quail-plumbing-inc-1126-26001867",
  "scraped_at": "2026-08-02T12:00:00.000Z",
  "country": "US",
  "address": "21831 N 16th Street, Phoenix, AZ 85024-5607",
  "phone": "(623) 780-2233",
  "email": null,
  "website": null,
  "categories": ["Plumber", "Drain Cleaning"],
  "description": "This business offers full service 24-hr plumbing.",
  "bbb_rating": "A+",
  "is_accredited": true,
  "years_in_business": 35,
  "review_rating": null,
  "review_count": null,
  "complaint_count": null,
  "reviews": [],
  "complaints": []
}
```

Additional structured fields are always present in every output row, including management contacts, additional websites, payment methods, and hours.

### Pricing

This actor is configured for predictable pay-per-event pricing:

| Event | Price |
|---|---:|
| Actor start | `$0.00005` per run scaled by allocated memory |
| BBB profile scraped | `$0.02` per normalized profile record |

The actor charges only after a complete schema-consistent profile record is stored. The run logs the maximum possible profile charge before crawling and stops gracefully if an Apify PPE event limit is reached. Heavy users may also use Apify's usage-based compute and proxy billing when enabled on the actor.

### Proxy configuration

BBB pages can be protected by rate limits or bot checks. The default uses Apify residential routing for US directory content. For an external proxy, set these environment variables in the actor's runtime configuration rather than placing credentials in input or source code:

```text
BBB_PROXY_USERNAME
BBB_PROXY_PASSWORD
BBB_PROXY_HOST
BBB_PROXY_PORT
```

The actor uses the supplied external proxy when both username and password are available; otherwise it falls back to Apify residential routing. Use a country-appropriate proxy for Canadian content.

### Reliability and limitations

- The actor uses Playwright, a session pool, persistent cookies per session, randomized viewport sizes, delays, and retry handling for transient requests.
- BBB is a public directory, but profile completeness varies. Some profiles expose phone and website links but no email, review count, or complaint count.
- Review and complaint parsing is intentionally limited to content publicly rendered on the profile or complaints tab. It does not bypass login or private data controls.
- `maxResults` is a hard cap on records and billing. A search page may be visited without producing a record when it contains no public profile links.
- Use reasonable page limits and respect BBB's public site rules, published policies, and rate limits.

### Run locally

```bash
npm install
npm run build
apify run
```

### Run through the API

```bash
curl -X POST "https://api.apify.com/v2/acts/USERNAME~bbb-business-scraper/runs?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"searchQuery":"plumber","location":"Phoenix, AZ","maxResults":10}'
```

Export the dataset as JSON, CSV, Excel, or connect the actor to downstream workflows through the Apify API, webhooks, schedules, or MCP-compatible integrations.

Export scraped data, run the scraper via API, schedule and monitor runs, or integrate with other tools.

# Actor input Schema

## `searchQuery` (type: `string`):

Use this when you want BBB businesses matching a category, company name, keyword, phone number, website, or email. Use startUrls instead when you already have BBB search or profile URLs.

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

Use this with searchQuery to limit BBB results to a city, state, ZIP code, or postal code. Leave blank when the supplied searchQuery is a business identifier or when using startUrls.

## `country` (type: `string`):

Use US for United States BBB pages or CA for Canadian BBB pages. This changes the BBB directory country filter for search URLs.

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

Use this when you have BBB search URLs or individual /us/.../profile/... or /ca/.../profile/... URLs. The actor discovers profiles from search pages and parses direct profile URLs. Use searchQuery and location instead for keyword search.

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

Use this to cap the total number of business profile records returned and charged. The actor stops after this many schema-consistent records, regardless of how many search pages are supplied.

## `maxPagesPerSearch` (type: `integer`):

Use this to control pagination for BBB search URLs and generated searches. Each additional page can discover more profile URLs and browser requests.

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

Use this when you need the latest visible customer review snippets from each BBB profile. It adds page parsing work and returns an empty array when no public reviews are shown.

## `includeComplaints` (type: `boolean`):

Use this when you need public complaint summaries from each BBB profile. It visits the profile's complaints tab when available and returns an empty array when no public complaint records are displayed.

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

Optional Crawlee proxy configuration. If omitted, the actor uses the supplied BBB\_PROXY\_\* environment variables when present, otherwise Apify residential US proxy routing.

## Actor input object example

```json
{
  "searchQuery": "roofing contractor",
  "location": "Austin, TX",
  "country": "US",
  "startUrls": [
    {
      "url": "https://www.bbb.org/us/az/phoenix/profile/plumber/pruitts-plumbing-1126-29002758"
    }
  ],
  "maxResults": 50,
  "maxPagesPerSearch": 5,
  "includeReviews": true,
  "includeComplaints": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `results` (type: `string`):

Schema-consistent BBB business profile records.

## `summary` (type: `string`):

Run counters and warnings stored under OUTPUT.

# 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 = {
    "searchQuery": "plumber",
    "location": "Phoenix, AZ",
    "country": "US",
    "startUrls": [
        {
            "url": "https://www.bbb.org/us/az/phoenix/profile/plumber/quail-plumbing-inc-1126-26001867"
        }
    ],
    "maxResults": 10,
    "maxPagesPerSearch": 3,
    "includeReviews": false,
    "includeComplaints": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("muhammadafzal/bbb-business-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 = {
    "searchQuery": "plumber",
    "location": "Phoenix, AZ",
    "country": "US",
    "startUrls": [{ "url": "https://www.bbb.org/us/az/phoenix/profile/plumber/quail-plumbing-inc-1126-26001867" }],
    "maxResults": 10,
    "maxPagesPerSearch": 3,
    "includeReviews": False,
    "includeComplaints": False,
}

# Run the Actor and wait for it to finish
run = client.actor("muhammadafzal/bbb-business-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 '{
  "searchQuery": "plumber",
  "location": "Phoenix, AZ",
  "country": "US",
  "startUrls": [
    {
      "url": "https://www.bbb.org/us/az/phoenix/profile/plumber/quail-plumbing-inc-1126-26001867"
    }
  ],
  "maxResults": 10,
  "maxPagesPerSearch": 3,
  "includeReviews": false,
  "includeComplaints": false
}' |
apify call muhammadafzal/bbb-business-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,muhammadafzal/bbb-business-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/2SSarA6cc3Nmsnpqu/builds/rDcajQ50m15Wddqmm/openapi.json
