# BBB Scraper - Business Ratings, Complaints & Reviews (US/CA) (`neverempty/bbb-business-ratings-scraper`) Actor

For lead lists, vendor checks and reputation research: BBB businesses by keyword and city or ZIP with BBB rating, accreditation, phone, address, review stars and complaints (total and last 12 months). US and Canada. Export as JSON, CSV or Excel.

- **URL**: https://apify.com/neverempty/bbb-business-ratings-scraper.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Lead generation, Business
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $12.00 / 1,000 business returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## BBB Scraper - Business Ratings, Complaints & Reviews (US/CA)

**For lead lists, vendor checks and reputation research:** Better Business Bureau (BBB) data for any business type and place in the US and Canada: **business name, BBB letter rating, BBB Accredited or not, category, address, phone, review count and average stars, total complaints and complaints in the last 12 months, resolved and unanswered complaints, and complaint types**, one row per business. **Type what you would type in the BBB search box and where** (for example Plumber and Roofing Contractors in Austin, TX, Dallas, TX and 78701) and every combination is collected in one run, with each business returned once.

Search and the review and complaint numbers come in the **same row from the same run**, so there is no second tool to feed profile links into. Filter to **BBB Accredited only** or a **minimum BBB rating** and the businesses left out are not charged.

### Input

| Field | What it does |
|---|---|
| `keywords` | What you would type in the BBB search box: a business type (`Plumber`, `Roofing Contractors`, `Auto Repair`, `Dentist`) or a business name |
| `locations` | US or Canadian city with state or province (`Austin, TX`, `Toronto, ON`) or a ZIP code (`78701`). Every keyword is searched in every location |
| `startUrls` | Optional. BBB search pages (`https://www.bbb.org/search?find_text=plumber&find_loc=Austin%2C%20TX`) or business profile pages (`https://www.bbb.org/us/tx/austin/profile/plumber/<name>-0825-1000223803`, its review and complaint pages included) |
| `maxResultsPerSearch` | Most businesses per search (one keyword in one location). Empty = 50, up to 225 |
| `includeDetails` | On (default): review count, average stars, stars breakdown, complaint count, complaints in the last 12 months, resolved / answered / unanswered and complaint types. Off: the search list fields only (faster) |
| `accreditedOnly` | Only BBB Accredited businesses. Others are not charged |
| `minRating` | Only businesses with this BBB letter rating or better (`A-` means A+, A and A-). Others are not charged |

Example: `{"keywords": ["Plumber", "Roofing Contractors"], "locations": ["Austin, TX", "78701"], "maxResultsPerSearch": 100, "minRating": "B"}` runs four searches.

Up to 25 searches (keywords x locations + start URLs) per run. The same keyword written twice (`Plumber`, `plumber`) is searched once, and a business that appears in several searches is returned only the first time.

### What you get

| Field | Example |
|---|---|
| `businessName` | `Calixto Plumbing` |
| `bbbRating`, `bbbRatingScore` | `A+`, `98.49` |
| `accredited` | `true` |
| `category`, `categories` | `Plumber`, `["Plumber", "Commercial Plumber", ...]` |
| `phone`, `phones` | `(512) 945-3143` (business phone as shown on BBB) |
| `address`, `city`, `state`, `postalCode`, `country` | `8410 Georgian Dr # B`, `Austin`, `TX`, `78753-6003`, `US` |
| `latitude`, `longitude`, `serviceAreas` | Map point and the service areas BBB lists |
| `reviewCount`, `reviewAverageStars`, `reviewStars` | `17`, `3.88`, `{"1": 3, "2": 2, "3": 0, "4": 1, "5": 11}` |
| `complaintCount`, `complaintsLast12Months` | All complaints BBB shows for the business (BBB keeps about 3 years), and those filed in the last 12 months |
| `complaintsResolved`, `complaintsAnswered`, `complaintsUnanswered`, `complaintTypes` | Complaint status counts and types (`Service or Repair Issues: 31`) |
| `localBbb`, `isCharity`, `outOfBusiness` | The local BBB office and flags |
| `bbbUrl`, `businessId`, `bbbId` | The BBB profile page |
| `positionInSearch`, `resultsInSearch`, `searchKeyword`, `searchLocation` | Which search found it and where it sat |
| `status`, `detailsNote`, `note`, `fetchedAt` | `ok` for a business row; see the table below |

The **overview** view of the dataset shows the columns used for calling and sorting; the **all fields** view shows everything.

### Why this one

- **One run, one row per business**: search list, review numbers and complaint numbers together.
- **Review average from every review**: counted from the stars of each review BBB shows.
- **Complaints in the last 12 months**, next to the total, so a recent problem stands out.
- **No duplicates**: a business listed on several pages or found by several searches is returned and charged once per run.
- **Filters are not charged**: businesses left out by `accreditedOnly` or `minRating` cost nothing.
- **A clear row when something is missing**: if a place is unknown, a list is cut or a page cannot be read, a free row says so.

### What is charged

- **business-returned**: one per business row with `status: ok`.
- **actor-start**: $0.005 once per run, only when at least one business is returned.
- Rows with any other status are free.

| Plan | Per 1,000 businesses |
|---|---|
| Free | $15.00 |
| Bronze | $14.00 |
| Silver | $13.00 |
| Gold and above | $12.00 |

If you set a maximum total charge, the run stops before it and says so in a `budget-reached` row. Nothing beyond the limit is charged.

### Rows that explain instead of failing

These rows are never charged. The `note` says what happened and what to do next.

| status | When |
|---|---|
| `no-results` | BBB lists no business for this keyword at this place |
| `not-found` | BBB does not recognise the location, or the business page is not on BBB. Check the spelling, or use a ZIP code |
| `more-not-returned` | Fewer businesses were returned than BBB lists: BBB's search shows only the first 225 of a search, or filters left some out. Search a more specific keyword, or several ZIP codes or nearby cities |
| `incomplete` | One page of a list could not be read. The businesses above the row were returned; run again later for the rest |
| `details-unavailable` | One business's reviews or complaints could not be read. It was not returned or charged |
| `unreadable` / `blocked` | BBB could not be read. Nothing is guessed. If reviews and complaints cannot be read for the first businesses, the run stops; run again later, or turn off `includeDetails` |
| `budget-reached` | The run's maximum total charge was reached; the businesses and searches not returned are listed |
| `bad-input` | The input could not be used, so nothing was requested |

### FAQ

**How do I get a list of BBB rated plumbers in Austin with phone numbers?**
Run it with `keywords: ["Plumber"]` and `locations: ["Austin, TX"]`. Each row has the business phone, BBB rating, accreditation, address and the review and complaint numbers.

**How do I get only BBB Accredited businesses rated A or better?**
Set `accreditedOnly: true` and `minRating: "A"`. Businesses outside the filter are not returned or charged.

**How many complaints did a business get in the last 12 months?**
Put its BBB profile link in `startUrls`. The row has `complaintCount`, `complaintsLast12Months`, the resolved / answered / unanswered counts and the complaint types.

**Can I search several business types in several cities at once?**
Yes. Every keyword is searched in every location in one run (up to 25 searches), and a business found in two searches is returned once.

**Does it work for Canada?**
Yes. Use a Canadian city with its province, for example `Toronto, ON`. `country` is `CA` on those rows.

**Do I need a BBB account?**
No. Only data anyone can see on bbb.org without signing in is read.

### Good to know

- Rows can contain business phone numbers and addresses that BBB shows publicly. For a sole proprietor these may be personal information; you are responsible for using them lawfully (for example GDPR, CCPA, TCPA and do-not-call rules).
- BBB's search shows at most 225 businesses (15 pages) per search. For a big city, search several ZIP codes or nearby cities.
- With `includeDetails` on, 400 businesses took about 8 minutes in our test. Turn `includeDetails` off for the search list fields only, which is much faster.
- Website, accreditation date, year founded and owner names are not returned.
- Review text, reviewer names and complaint text are not returned; the counts, stars and complaint types are.
- `complaintCount` is what BBB shows on the business's complaint page (BBB keeps complaints for about 3 years), including complaints BBB counts but does not publish.
- If BBB's review list changes while it is being read, `reviewStars` and `reviewAverageStars` are left empty with a reason in `detailsNote` instead of being guessed.
- Phone numbers are the business numbers BBB shows. Email addresses are not returned.
- This Actor is not affiliated with, endorsed by or sponsored by the Better Business Bureau. BBB's terms of use restrict use of its content, including republication and aggregation; decide for yourself whether your use is permitted before you run it.

### Thanks for using this Actor

We build these tools for people who run them every day, and we improve them from what users tell us.

- **Missing a field, or need another filter?** Tell us in the **Issues** tab. If the data is there, we add it.
- **Found a bug or a wrong value?** Post the run ID in the **Issues** tab. Wrong data is the thing we fix first.

If this Actor saved you time, a short review helps other people find it.

# Actor input Schema

## `keywords` (type: `array`):

What you would type in the BBB search box: a business type (Plumber, Roofing Contractors, Auto Repair, Dentist) or a business name. Each keyword is searched in each location. Leave Keywords, Locations and Start URLs all empty to run the example search Plumber in Austin, TX.

## `locations` (type: `array`):

US or Canadian city with state or province (Austin, TX / Toronto, ON) or a ZIP code (78701). Keywords times locations make the searches of a run (up to 25 per run).

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

Optional. BBB search pages (https://www.bbb.org/search?find_text=plumber\&find_loc=Austin%2C%20TX) or business profile pages (https://www.bbb.org/us/tx/austin/profile/plumber/<name>-0825-1000223803) copied from your browser.

## `maxResultsPerSearch` (type: `integer`):

How many businesses to return for each keyword and location. BBB's search shows at most 225 businesses (15 pages) for one search; for more, add ZIP codes or nearby cities.

## `includeDetails` (type: `boolean`):

On: each business also gets its review count, average stars, stars breakdown, complaint count, complaints in the last 12 months, answered/resolved counts and complaint types. Off: only the search list fields (faster).

## `accreditedOnly` (type: `boolean`):

Return only businesses that are BBB Accredited.

## `minRating` (type: `string`):

Return only businesses with this BBB letter rating or better. Businesses without a rating (NR) are left out when a rating is chosen.

## Actor input object example

```json
{
  "keywords": [
    "Plumber"
  ],
  "locations": [
    "Austin, TX"
  ],
  "maxResultsPerSearch": 50,
  "includeDetails": true,
  "accreditedOnly": false,
  "minRating": "ANY"
}
```

# Actor output Schema

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

One row per business: name, BBB rating and score, accredited, category, address, phone, review count, average stars and stars breakdown, complaint count, complaints in the last 12 months, resolved/answered/unanswered, complaint types and the BBB profile link. A search with no businesses, a place BBB does not know, a refused request or a run that hit its maximum charge comes back as a free row that says why.

# 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 = {
    "keywords": [
        "Plumber"
    ],
    "locations": [
        "Austin, TX"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/bbb-business-ratings-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 = {
    "keywords": ["Plumber"],
    "locations": ["Austin, TX"],
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/bbb-business-ratings-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 '{
  "keywords": [
    "Plumber"
  ],
  "locations": [
    "Austin, TX"
  ]
}' |
apify call neverempty/bbb-business-ratings-scraper --silent --output-dataset

```

## MCP server setup

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