# BBB Scraper - Ratings, Reviews, Complaints & Business Profiles (`s-r/bbb-scraper`) Actor

BBB scraper for US and Canadian businesses: letter grade, accreditation and date, address, phone, website, star rating and review count, plus optional customer reviews and complaints with business responses. For reputation monitoring, contractor leads and due diligence.

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

## Pricing

Pay per event

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?

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 – Ratings, Reviews, Complaints and Business Profiles

BBB Scraper collects Better Business Bureau profiles from the United States and Canada, with optional customer reviews and public complaints. Build a dataset for reputation monitoring, contractor research, lead generation or business due diligence. Each business keeps its BBB identifier so you can connect the profile to its reviews, complaints and responses.

### What the BBB Scraper collects

Business records include the public business name, canonical profile URL, address, phone number, website, business categories, BBB letter grade, accreditation status and accreditation date. Detailed profiles also include the customer star average, review count, complaint totals, decoded public email addresses and other available profile information. Availability depends on what BBB publishes for the individual business.

Customer reviews are separate records with their star rating, full text, public author name, publication date and business response when one is available. Complaints are also separate records. They include a complaint category, BBB status, publication date, complaint text and the available response transcript. A complaint has no star rating. Text masking applied by BBB remains in the output, and source HTML is preserved in complaint text and replies.

The BBB letter grade and customer star rating measure different things. `bbb_rating` contains the letter grade, such as A+ or NR. `rating` contains the customer star average or the stars on an individual review. Businesses with no published review average have a null star rating, rather than zero. These fields let you compare reputation signals without mixing incompatible scales.

### Use cases

- **Reputation monitoring.** Track the BBB rating, accreditation, review count and new complaints of your own company, your franchise locations or your competitors. Business responses come with each review and complaint, so you can see how quickly and how well a company answers.
- **Lead generation for contractors and home services.** Search a category such as roofing, plumbing or HVAC in a city and get a list of local businesses with phone, website, address, accreditation and years in business. The BBB grade helps you rank or filter the list.
- **Due diligence and vendor checks.** Before signing with a supplier, contractor or acquisition target, export its BBB profile, licences, principal contacts and complaint history into one file.
- **Market and consumer research.** Collect complaint categories and review texts across a whole sector to see which issues customers raise most often.
- **Enriching other datasets.** Phone numbers and street addresses let you join BBB records to Google Maps data or your CRM, adding the letter grade and complaint counts that map listings do not show.

### How to scrape BBB reviews and complaints

1. Choose how to find businesses: search terms with a city, BBB search or category URLs, or a list of profile URLs.
2. Set `limit` to the number of businesses you need.
3. Switch on `include_reviews` and/or `include_complaints` and choose a maximum per business.
4. Start the run and download the dataset as JSON, CSV, Excel or through the API.

### Input

Choose one or combine several of these input routes:

- `search_terms` with `location`: use categories, keywords or business names and a city with state or province. Set `country` to USA or CAN. For example, roofing in Denver, CO and plumber in Toronto, ON are supported searches.
- `search_urls`: supply BBB search URLs or category URLs. Existing search filters, sorting parameters and the starting page are retained.
- `profile_urls`: supply specific BBB business profiles. Links ending in customer-reviews, complaints or an addressId suffix are normalised to the business profile.

`limit` controls the total number of businesses across all inputs. It defaults to 50 and accepts values from 1 to 1000. Duplicate profiles count once, including businesses that appear multiple times in search results. A missing profile is recorded as a source error and does not create a business row.

`include_details` defaults to true. Turn it off to collect search cards with their public name, address, phone, category, BBB grade and accreditation flag. Search cards lack websites, customer star averages, review totals and accreditation dates, so those fields remain null. Direct profile URLs always return detailed profiles. The `sort` and `accredited_only` options apply to searches constructed from search terms; supplied search URLs keep their own filters.

Enable `include_reviews` and set `max_reviews` to collect up to that many customer reviews per business. Enable `include_complaints` and set `max_complaints` for public complaints. Both switches default to false. Each maximum defaults to 20 and accepts 0 to 1000; zero disables that collection. These rows do not reduce the business limit.

| field | type | default | meaning |
|---|---|---|---|
| `search_terms` | list of text | – | categories, keywords or business names |
| `location` | text | – | city with state or province, required with `search_terms` |
| `country` | USA or CAN | USA | country of the search |
| `search_urls` | list of URLs | empty | BBB search or category URLs |
| `profile_urls` | list of URLs | empty | BBB business profile URLs |
| `sort` | Relevance, Rating, Distance, AToZ, ZToA | Relevance | order of search results |
| `accredited_only` | true/false | false | only BBB-accredited businesses |
| `limit` | 1–1000 | 50 | businesses across all inputs |
| `include_details` | true/false | true | open each profile for the full record |
| `include_reviews` / `max_reviews` | true/false, 0–1000 | false, 20 | customer reviews per business |
| `include_complaints` / `max_complaints` | true/false, 0–1000 | false, 20 | public complaints per business |

A measured profile input is:

```json
{
  "profile_urls": [
    "https://www.bbb.org/us/co/lakewood/profile/remodel-contractors/rise-renovation-llc-1296-1000139866"
  ],
  "limit": 1,
  "include_reviews": true,
  "max_reviews": 10,
  "include_complaints": true,
  "max_complaints": 2
}
```

The captured responses for this input contain one Rise Renovation LLC business, ten customer reviews and two complaints. Public records and totals can change between runs.

### Output

The default dataset contains all delivered rows. `row_type` distinguishes place and review records. Review records use `review_type` to distinguish customer reviews from complaints. `_type` gives the corresponding local.place or local.review record type. The shared `place_id` and profile URL connect every feedback row to its business. `row_id` is a stable typed identity used when combining pages.

Detailed business rows expose extra information when the source publishes it: alternative business names, additional phone numbers and websites, social links, opening hours, entity type, business dates, licensing records, public contacts, service areas and head-office indicators. An absent value stays null or an empty list. Dates have the precision supplied by BBB: publication and accreditation dates use YYYY-MM-DD, while `fetched_at` is a UTC timestamp.

The OUTPUT record in the key-value store contains run information, delivered counts and continuation status. Warnings explain source restrictions or incomplete collection. Missing or unavailable pages appear in the errors record. Large collections are gathered in successive pages automatically by the Actor. API users can continue a response with its `cursor` while retaining the same input options.

#### Output example

These are real rows from the profile input above (captured on 5 October 2026). The business row shows a selection of its fields; additional phones, websites, social links, opening hours, licences and business dates were left out for length. The customer review is complete. The complaint row omits its long `text`, `reply_text` and `responses` fields.

Business row:

```json
{
  "_type": "local.place",
  "row_type": "place",
  "row_id": "place:1296-1000139866",
  "place_id": "1296-1000139866",
  "bbb_id": "1296",
  "business_id": "1000139866",
  "name": "Rise Renovation LLC",
  "url": "https://www.bbb.org/us/co/lakewood/profile/remodel-contractors/rise-renovation-llc-1296-1000139866",
  "address": "13949 W Colfax Ave Ste 250, Lakewood, CO 80401",
  "city": "Lakewood",
  "state": "CO",
  "postal_code": "80401",
  "country_code": "US",
  "category": "Remodel Contractors",
  "phone": "(720) 295-7451",
  "website": "https://riserenovation.com/",
  "emails": [
    "info@riserenovation.com",
    "nick@riserenovation.com"
  ],
  "rating": 3.75,
  "reviews_count": 16,
  "complaints_count": 2,
  "bbb_rating": "A+",
  "is_accredited": true,
  "accredited_since": "2023-01-03",
  "years_in_business": 4,
  "entity_type": "Limited Liability Company (LLC)",
  "contacts": [
    {
      "name": "Mr. Nick Ventura",
      "title": "Owner",
      "is_principal": true
    }
  ],
  "service_areas": [
    "Arvada, CO",
    "Boulder, CO",
    "Denver, CO",
    "Englewood, CO",
    "Golden, CO",
    "Littleton, CO",
    "Westminster, CO",
    "CO"
  ],
  "fetched_at": "2026-10-05T23:46:03.422Z"
}
```

Customer review row:

```json
{
  "_type": "local.review",
  "row_type": "review",
  "review_type": "review",
  "row_id": "review:1296_1000139866_841301",
  "source": "bbb-scraper",
  "platform": "bbb",
  "fetched_at": "2026-10-05T23:46:21.578Z",
  "review_id": "1296_1000139866_841301",
  "place_id": "1296-1000139866",
  "place_name": "Rise Renovation LLC",
  "place_url": "https://www.bbb.org/us/co/lakewood/profile/remodel-contractors/rise-renovation-llc-1296-1000139866",
  "rating": 1,
  "text": "On 12/29/2025, a representative wearing RISE branded hat and jacket approached my home, rang my doorbell, and remained on my property, despite a visible No Soliciting sign that he had blatantly walked past. This was captured on my Ring doorbell camera. Note that I did not request any services, and the visit was unsolicited.This disrespectful behavior is frustrating and disregards homeowners posted boundaries. Companies conducting door-to-door sales need to teach their representatives to respect local ordinances and private property notices.",
  "author_name": "A Concerned N",
  "published_at": "2025-12-29",
  "reply_text": null,
  "reply_date": null,
  "reply_title": null,
  "customer_followup": null,
  "review_guid": "11d61703-ca70-48c9-bdb0-38a64e26920a",
  "complaint_type": null,
  "complaint_status": null,
  "responses": []
}
```

Complaint row:

```json
{
  "_type": "local.review",
  "row_type": "review",
  "review_type": "complaint",
  "row_id": "complaint:1296_1000139866_24489987",
  "review_id": "1296_1000139866_24489987",
  "place_id": "1296-1000139866",
  "place_name": "Rise Renovation LLC",
  "rating": null,
  "published_at": "2026-02-05",
  "complaint_type": "Service or Repair Issues",
  "complaint_status": "Answered",
  "reply_date": "2026-02-06"
}
```

### BBB Scraper compared with other options

- **Copying from bbb.org by hand** works for a handful of companies, but reviews and complaints sit on separate paginated pages per business. This Actor collects them in one run and keeps the link to the business.
- **Google Maps scrapers** return ratings and reviews from Google, not the BBB letter grade, accreditation date or complaint history. Use the phone number or address to combine both sources.
- **Other BBB actors** often return search cards only, or nest reviews and complaints inside the business record. Here every review and complaint is its own row with a shared `place_id`, which makes filtering, counting and spreadsheet work straightforward, and you pay separately for businesses, reviews and complaints so you only pay for what you switch on.
- **Generic scraping tools** return raw pages that you still need to parse. This Actor returns typed fields with stable identifiers and clear null values.

### Pricing

Charging is based on delivered records, with separate events for business profiles, customer reviews and complaints. Consult the Actor's pricing panel for the current event prices. A business row counts once; each delivered review or complaint counts once under its own event. Empty results, unavailable profiles and diagnostic errors do not produce chargeable records. There is no run-start event in this product's charge map.

Free users receive up to ten dataset rows in total. Business, review and complaint records all count towards that allowance. If you enable feedback collection, it can therefore use part of the free allowance. Paid runs retain their requested business and feedback limits.

### Source coverage and practical limits

BBB shows at most fifteen search pages, normally fifteen businesses per page, for one search. Duplicate cards can reduce the number of distinct businesses below 225. Collecting a larger set requires additional terms, cities or source filters. A warning identifies this source cap when it is reached.

BBB customer reviews cover its public review window, observed as the last three years. Public complaint lists can be capped at 10000, and some complaints have no publicly readable text. A business being reviewed or updated by BBB may retain profile totals while hiding its existing feedback. In that case the business is delivered with a warning and no invented review or complaint rows.

### FAQ

#### Can I scrape Canadian businesses?

Yes. Use CAN with a Canadian city and province, or supply Canadian BBB search and profile URLs. Business records identify Canada with `country_code` CA and preserve Canadian postal codes.

#### Is the BBB grade the customer star rating?

No. The BBB grade is delivered as `bbb_rating`; the numeric customer star rating is `rating`. NR means BBB has not assigned a letter grade. A complaint has no stars and therefore a null rating.

#### What does a BBB rating mean?

The letter grade, from A+ to F, is BBB's own assessment of how a business deals with its customers, based on factors such as complaint history, time in business and transparency. Customer reviews are not used in that calculation. `rating_reasons` repeats the explanation BBB publishes on the profile.

#### Is there a BBB API?

This Actor works as a BBB API: call it through the Apify API with the same input as in the console and receive business profiles, reviews and complaints as structured JSON rows. API users can also continue a large collection with the returned `cursor`.

#### Can I search BBB complaints by business name?

Yes. Put the business name in `search_terms` with its city, or paste the business profile URL into `profile_urls`, and switch on `include_complaints`.

#### How do I join reviews and complaints to businesses?

Use `place_id`. Business records contain their canonical URL in `url`, and feedback records repeat it in `place_url`. A review also repeats the business name in `place_name` for convenient spreadsheet use.

#### Why can a profile show reviews without any review rows?

BBB sometimes hides feedback while it updates a profile. The collection reports that restriction as a warning. Other reasons include a disabled feedback option, a zero maximum, or the requested collection reaching its delivery limit.

# Actor input Schema

## `search_terms` (type: `array`):

Business names, categories or keywords. Requires location; each term starts a separate search.

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

City and state or province, for example Denver, CO or Toronto, ON. Required with search_terms.

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

Country for searches built from search_terms.

## `search_urls` (type: `array`):

BBB search or category URLs. Existing filters and the starting page are preserved.

## `profile_urls` (type: `array`):

BBB business profile URLs. Customer-review, complaint and addressId suffixes are normalised to the profile.

## `sort` (type: `string`):

Sort order for searches built from search_terms.

## `accredited_only` (type: `boolean`):

Require accreditation in searches built from search_terms.

## `limit` (type: `integer`):

Maximum businesses across all inputs. Review and complaint rows do not consume this limit.

## `include_details` (type: `boolean`):

Fetch full profiles for search results. Off returns search cards with null website, stars, review count and accreditation date. Direct profile URLs always return details.

## `include_reviews` (type: `boolean`):

Deliver customer reviews as separate rows linked to each business.

## `max_reviews` (type: `integer`):

Maximum reviews per business, newest first. Zero disables review collection.

## `include_complaints` (type: `boolean`):

Deliver public complaints as separate review rows with review_type complaint.

## `max_complaints` (type: `integer`):

Maximum public complaints per business. Zero disables complaint collection.

## `cursor` (type: `string`):

Opaque signed continuation token. Resubmit the same inputs with this token to continue; null starts a new collection.

## Actor input object example

```json
{
  "search_terms": [
    "roofing"
  ],
  "location": "Denver, CO",
  "country": "USA",
  "profile_urls": [
    "https://www.bbb.org/us/co/lakewood/profile/remodel-contractors/rise-renovation-llc-1296-1000139866"
  ],
  "sort": "Relevance",
  "accredited_only": false,
  "limit": 50,
  "include_details": true,
  "include_reviews": false,
  "max_reviews": 20,
  "include_complaints": false,
  "max_complaints": 20
}
```

# Actor output Schema

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

Typed business and feedback rows.

## `output` (type: `string`):

Delivered counts, warnings and continuation status.

## `errors` (type: `string`):

Missing or unavailable source pages.

# 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 = {
    "search_terms": [
        "roofing"
    ],
    "location": "Denver, CO",
    "profile_urls": [
        "https://www.bbb.org/us/co/lakewood/profile/remodel-contractors/rise-renovation-llc-1296-1000139866"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/bbb-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 = {
    "search_terms": ["roofing"],
    "location": "Denver, CO",
    "profile_urls": ["https://www.bbb.org/us/co/lakewood/profile/remodel-contractors/rise-renovation-llc-1296-1000139866"],
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/bbb-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 '{
  "search_terms": [
    "roofing"
  ],
  "location": "Denver, CO",
  "profile_urls": [
    "https://www.bbb.org/us/co/lakewood/profile/remodel-contractors/rise-renovation-llc-1296-1000139866"
  ]
}' |
apify call s-r/bbb-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/bbb-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/yApIv4jSZPtT3YSBJ/builds/s24VjjyjZBvEjhOXs/openapi.json
