# BBB Scraper — Ratings, Accreditation & Contacts (`khadinakbar/bbb-scraper`) Actor

Scrape Better Business Bureau (bbb.org) businesses by keyword + location or profile URL. Returns BBB rating, accreditation, phone, website, address & contacts. MCP/API-ready.

- **URL**: https://apify.com/khadinakbar/bbb-scraper.md
- **Developed by:** [Khadin Akbar](https://apify.com/khadinakbar) (community)
- **Categories:** Lead generation, MCP servers, AI
- **Features:** Limited permissions
- **Stats:** 10 total users, 2 monthly users, 99.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 business founds

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

### What is BBB Scraper — Ratings, Accreditation & Contacts?

BBB Scraper extracts Better Business Bureau (bbb.org) business profiles into a dataset, one row per business, from a keyword plus location search or from profile URLs. Each row returns the BBB letter rating, accreditation status and date, years in business, entity type, categories, phone, website, full address with coordinates, opening hours, complaint counts and listed management contacts. Lead-generation teams, vendor due-diligence analysts, reputation researchers and AI agents use it to build trusted lists of US and Canadian local businesses.

### Why teams use this BBB scraper

- **Trust signals in one row.** `bbbRating`, `isAccredited`, `dateAccredited` and `yearsInBusiness` show standing at a glance.
- **Complaint history.** `complaintsCount`, `complaintsClosedLast3Years` and `complaintsClosedLast12Months` support due-diligence checks.
- **Outreach-ready contacts.** `phone`, `website`, `formattedAddress` and `contacts` (name and title) come with each enriched profile.
- **Map-ready.** `latitude` and `longitude` place each business on a map.
- **Filters for clean lists.** `minRating` and `accreditedOnly` keep only the businesses you want.
- **Two depths.** Enriched profiles by default, or lighter listing rows with `enrichDetails` set to `false`.

### Best fit and related Actors

Best fit for US and Canadian service businesses where BBB standing matters: contractors, plumbers, HVAC, roofers, movers and other local trades, plus supplier checks before signing a contract.

- For broader local coverage with reviews and photos, use [Google Maps Leads Scraper](https://apify.com/khadinakbar/google-maps-leads-scraper) alongside this Actor.
- To find email addresses behind the `website` column, then pass the websites to [Bulk Website Contact Extractor](https://apify.com/khadinakbar/bulk-website-contact-extractor).

### Example workflow: from a trade and city to a vetted lead list

A home-services marketplace starts with `searchKeyword` set to `plumber`, `location` set to `Los Angeles, CA`, `accreditedOnly` set to `true` and `minRating` set to `A`. The dataset returns each plumber's `businessName`, `bbbRating`, `yearsInBusiness`, `phone`, `website` and `complaintsCount`. They sort by `yearsInBusiness`, drop businesses with open complaints, and export the shortlist to their partner-onboarding sheet. A monthly schedule per metro area keeps the list current and lets them compare ratings over time.

### How to scrape BBB

1. Enter a **Search keyword** such as `hvac contractor` and a **Location** as `City, ST`, or paste profile URLs in **BBB profile URLs**.
2. Choose **Country**, set **Max results**, and optionally **Minimum BBB rating** or **BBB-accredited businesses only**.
3. Click **Start**, then export the dataset as JSON, CSV or Excel, or read it through the API.

### Input

```json
{
  "searchKeyword": "plumber",
  "location": "Los Angeles, CA",
  "country": "US",
  "maxResults": 20,
  "enrichDetails": true,
  "minRating": "Any",
  "accreditedOnly": false
}
```

| Field | Type | What it controls |
|---|---|---|
| `searchKeyword` | string | Business type or name, for example `plumber` or `general contractor`. |
| `location` | string | `City, ST` for the US or `City, Province` for Canada. Optional. |
| `country` | string | `US` or `CA`. Default `US`. |
| `startUrls` | array | Direct `bbb.org/.../profile/...` URLs to scrape in depth. |
| `maxResults` | integer | Businesses to return across the run. Default 100. |
| `enrichDetails` | boolean | Open each profile for full detail. Default `true`. |
| `minRating` | string | Minimum BBB letter rating, from `A+` to `F`, or `Any`. |
| `accreditedOnly` | boolean | Keep only BBB-accredited businesses. Default `false`. |
| `storeRawSample` | boolean | Save a raw page sample to the key-value store for inspection. Default `false`. |
| `proxyConfiguration` | object | Apify Residential proxy pinned to the chosen country by default. |

### Output

One dataset row per business.

| Field | What it contains |
|---|---|
| `businessName`, `altBusinessNames` | Business name and other registered names |
| `bbbId`, `url` | BBB business ID and profile URL |
| `bbbRating` | Letter rating from A+ to F |
| `isAccredited`, `accreditationStatus`, `dateAccredited` | Accreditation flag, label and date |
| `businessStarted`, `incorporatedDate`, `bbbFileOpened`, `yearsInBusiness` | Business history |
| `entityType` | Corporation, LLC and similar |
| `categories`, `primaryCategory` | BBB business categories |
| `phone`, `website` | Contact details |
| `street`, `city`, `state`, `postalCode`, `country`, `formattedAddress` | Address |
| `latitude`, `longitude` | Coordinates |
| `complaintsCount`, `complaintsClosedLast3Years`, `complaintsClosedLast12Months` | Complaint history |
| `ratingCount`, `averageStarRating` | Customer review signals when published |
| `contacts` | Listed management contacts with name and title |
| `hours` | Opening hours by weekday |
| `logo` | Business logo when published |
| `scrapedFrom`, `scrapedAt` | `profile` or `search`, and the collection time |

The schema also includes `email`, `numberOfEmployees`, `additionalWebsites` and `servingArea`, filled when the BBB profile publishes them.

```json
{
  "businessName": "Fords Plumbing",
  "altBusinessNames": ["Robert Ford Plumbing And Heating, Inc."],
  "bbbId": "1216-100081178",
  "url": "https://www.bbb.org/us/ca/los-angeles/profile/plumber/fords-plumbing-1216-100081178",
  "bbbRating": "A+",
  "isAccredited": true,
  "accreditationStatus": "BBB Accredited Business",
  "dateAccredited": "2010-10-04",
  "businessStarted": "1962-09-11",
  "incorporatedDate": "2007-08-06",
  "yearsInBusiness": 64,
  "entityType": "Corporation",
  "categories": ["Plumber", "Sewer Contractors", "Leak Detection", "Water Heater Installation", "Drain Cleaning"],
  "primaryCategory": "Plumber",
  "phone": "(310) 815-1515",
  "email": null,
  "website": "https://www.fordsplumbingandheating.com/",
  "street": "5818 Venice Blvd",
  "city": "Los Angeles",
  "state": "CA",
  "postalCode": "90019-5019",
  "country": "US",
  "formattedAddress": "5818 Venice Blvd, Los Angeles, CA 90019-5019",
  "latitude": 34.049841,
  "longitude": -118.33846,
  "ratingCount": 0,
  "averageStarRating": null,
  "complaintsCount": 1,
  "complaintsClosedLast3Years": 1,
  "complaintsClosedLast12Months": 1,
  "contacts": [
    { "name": "Jordan Example", "title": "President" },
    { "name": "Casey Example", "title": "Manager" }
  ],
  "hours": { "monday": "Open 24 Hours", "tuesday": "Open 24 Hours" },
  "logo": null,
  "scrapedFrom": "profile",
  "scrapedAt": "2026-10-01T13:34:50.492Z"
}
```

Contact names in this example are fictional placeholders.

### Pricing

This Actor uses Pay per event with Apify platform usage included in the event price. Each business is charged once: a **Profile enriched** event when its full profile is scraped (the default, and every profile URL), or a **Business found** event when `enrichDetails` is `false` and only listing fields are returned. Each run also has one **Actor Start** event.

Worked example in words: an enriched search capped at twenty businesses costs twenty Profile enriched events plus one Actor Start event. The same search with enrichment off costs twenty Business found events instead. Check the live Pricing tab on this page for current event prices.

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/khadinakbar~bbb-scraper/run-sync-get-dataset-items?token=YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"searchKeyword": "hvac contractor", "location": "Austin, TX", "maxResults": 25, "accreditedOnly": true}'
```

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_API_TOKEN")
run = client.actor("khadinakbar/bbb-scraper").call(run_input={
    "searchKeyword": "roofing contractor",
    "location": "Toronto, ON",
    "country": "CA",
    "maxResults": 30,
    "minRating": "A",
})
for biz in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(biz["businessName"], biz["bbbRating"], biz["phone"], biz["yearsInBusiness"])
```

### Use with AI agents (MCP)

Connect the Actor to Claude, ChatGPT, Cursor or any MCP client through [Apify MCP](https://mcp.apify.com) and ask:

> Find 20 BBB-accredited plumbers in Los Angeles with an A rating or better. Return name, rating, years in business, phone, website and complaint count.

The agent sets `searchKeyword`, `location`, `accreditedOnly` and `minRating`, reads rows back from the dataset, and cites each business by `url` and `scrapedAt`. `maxResults` sets the cost scope before the run starts.

### Best results

| Starting situation | Helpful action | Expected outcome |
|---|---|---|
| A large metro target | Run one search per city or suburb and merge on `bbbId` | Wider coverage than a single search page set |
| Listing fields are enough | Set `enrichDetails` to `false` | Faster, lighter rows |
| A vetted partner list | Combine `accreditedOnly` with `minRating` set to `A` | Only well-rated accredited businesses |
| Known businesses to check | Paste their profile URLs in `startUrls` | Full detail for exactly those businesses |
| Canadian markets | Set `country` to `CA` and `location` to `City, Province` | Results from the Canadian BBB directory |
| Due diligence | Read `complaintsClosedLast12Months` and `dateAccredited` together | A quick view of recent standing |

### Builder's note

I built this Actor around BBB's trust signals because they are what Google Maps data leaves out: accreditation, letter ratings, complaint history and business age. I designed enrichment as the default since contacts, hours and history live on the profile page, and kept a lighter listing mode with its own event for runs that only need names and ratings. I found that rating and accreditation filters are most useful after enrichment, so they apply once each profile is read.

### FAQ

#### Which countries does it cover?

The United States and Canada, chosen with `country`.

#### How do I collect more than one search returns?

Run several locations, for example one per metro area or suburb, and merge the datasets on `bbbId`.

#### Why are some fields empty?

BBB publishes different details for different businesses. Fields such as `email` and `averageStarRating` are filled when the profile shows them.

#### Which proxy should I use?

Keep the default Apify Residential proxy pinned to the chosen country.

### Responsible use

This Actor collects publicly available business-directory information from bbb.org. Use it in line with BBB's terms of service and applicable laws. Profiles can list names of owners and managers, which are personal data under laws such as the CCPA and Canada's PIPEDA, so use them only with a lawful basis and honor opt-outs before outreach. BBB is a trademark of its owner; this independent Actor is not affiliated with or endorsed by the Better Business Bureau.

# Changelog

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

# Actor input Schema

## `searchKeyword` (type: `string`):

Business type or name to search on bbb.org, e.g. 'plumber', 'hvac contractor', or a company name. Combine with 'location' to scope by city. Leave empty if you instead pass profile URLs in 'startUrls'. NOT a URL — for a single known business use 'startUrls'.

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

Geographic area to scope the search, formatted as 'City, ST' for the US or 'City, Province' for Canada, e.g. 'Los Angeles, CA'. Optional — omit for a nationwide search. Ignored when only profile 'startUrls' are provided. NOT a ZIP code on its own.

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

Country directory to search: US (bbb.org USA) or CA (Canada). Defaults to US. Also pins the residential proxy region. Does not affect direct profile 'startUrls', which already encode their country.

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

Direct bbb.org business profile URLs to scrape in depth, e.g. https://www.bbb.org/us/ca/los-angeles/profile/plumber/example-1216-907682 . Use instead of, or alongside, a keyword search. NOT search or category URLs — only '/profile/' pages are scraped here.

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

Maximum number of businesses to return across the whole run. Defaults to 100. A single keyword+location search caps at 225 results on bbb.org; raise this and add more locations to collect more. Controls cost — you are billed per returned business.

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

When true (default), each business found in search is opened and its full profile is scraped (accreditation date, years in business, hours, owner contacts). When false, only the lighter search-listing fields are returned, which is faster and cheaper. Ignored for direct profile 'startUrls' (always enriched).

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

Only return businesses with at least this BBB letter rating (A+ is highest, F lowest). Defaults to 'Any' (no filter). Businesses with no published rating are excluded when a minimum is set. Applied after scraping each business.

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

When true, return only BBB-Accredited businesses and skip the rest. Defaults to false (return all). Useful for lead lists that require accreditation. Applied after each business is scraped.

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

Developer/debug option. When true, the first search and profile page's raw embedded JSON is saved to the key-value store for inspection. Defaults to false. Leave off for normal runs — it does not change the dataset output.

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

Advanced. Proxy settings. Defaults to Apify Residential proxy pinned to the chosen country, which is required to bypass bbb.org's Cloudflare protection. Override only if you know what you are doing — datacenter proxies are blocked by BBB.

## Actor input object example

```json
{
  "searchKeyword": "general contractor",
  "location": "Austin, TX",
  "country": "US",
  "startUrls": [
    "https://www.bbb.org/us/ca/los-angeles/profile/plumber/l-a-tamix-plumbing-1216-907682"
  ],
  "maxResults": 20,
  "enrichDetails": true,
  "minRating": "Any",
  "accreditedOnly": false,
  "storeRawSample": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

No description

## `keyValueStore` (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 = {
    "searchKeyword": "plumber",
    "location": "Los Angeles, CA",
    "startUrls": [],
    "maxResults": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("khadinakbar/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 = {
    "searchKeyword": "plumber",
    "location": "Los Angeles, CA",
    "startUrls": [],
    "maxResults": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("khadinakbar/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 '{
  "searchKeyword": "plumber",
  "location": "Los Angeles, CA",
  "startUrls": [],
  "maxResults": 20
}' |
apify call khadinakbar/bbb-scraper --silent --output-dataset

```

## MCP server setup

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