# BBB Scraper — Better Business Bureau Ratings, Complaints, Leads (`oswaldocarabano/bbb-business-scraper`) Actor

Scrape Better Business Bureau business profiles (bbb.org, US & Canada): BBB rating, accreditation, complaints and reviews closed, years in business, entity type, owners, phone, website, address and coordinates. 76 fields per business, read from BBB's own sitemaps. No login, no proxy, no browser.

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

## Pricing

from $3.00 / 1,000 business delivereds

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 — Better Business Bureau business data, ratings & complaints

Scrape business profiles from the **Better Business Bureau** (`bbb.org`) across
the **United States and Canada**: BBB letter rating, accreditation status and
date, **complaints and customer reviews closed**, years in business, legal entity
type, **owners and officers**, phone, website, full address and coordinates.

**76 fields per business.** Most BBB scrapers return the ~20 fields BBB's search
box gives away. This one also reads the profile page, where the other 35 live.

BBB has **no public API**. This actor is the practical substitute: a BBB business
data API you can call from the Apify API, a scheduler, Make, n8n, Zapier, or your
own code, with the output shaped as one clean row per business.

***

### What one row looks like

```json
{
  "business_name": "Al Mac Plumbing & Gas, LLC",
  "legal_name": "Al Mac Plumbing & Gas, LLC",
  "category_name": "Plumber",
  "category_id": "10113-000",
  "categories": [{ "tob_id": "10113-000", "name": "Plumber" }],
  "rating": "A+",
  "rating_score": 100,
  "is_accredited": true,
  "accreditation_date": "2024-12-02",
  "complaints_closed_3y": 0,
  "complaints_closed_12m": 0,
  "reviews_total": 1,
  "review_average_stars": 5,
  "years_in_business": 5,
  "type_of_entity": "Limited Liability Company (LLC)",
  "business_started": "2020-05-01",
  "bbb_file_opened": "2021-03-15",
  "principals": [{ "name": "Mr. Alex McCarra", "title": "Managing Member" }],
  "phone": ["(512) 555-0123"],
  "phone_e164": ["+15125550123"],
  "website": "https://www.almacatxplumbing.com/",
  "address": "10415 Old Manchaca Rd Ste 205",
  "city": "Austin",
  "state": "TX",
  "postal_code": "78748-1448",
  "latitude": 30.3,
  "longitude": -97.76,
  "service_areas_summary": ["78730", "78731", "78732"],
  "accepts_quote_requests": true,
  "has_service_area": true,
  "is_multi_location": false,
  "locations": [],
  "report_url": "https://www.bbb.org/us/tx/austin/profile/plumber/al-mac-plumbing-gas-llc-0825-1000194310"
}
```

*(Phone replaced with a 555-01xx number, which is reserved for fiction. This page
does not reproduce anyone's real contact details.)*

***

### What you can do with it

- **Build B2B lead lists** of local service businesses — plumbers, roofers, HVAC,
  electricians, general contractors, movers, dentists, lawyers, auto repair — by
  city, state or trade, with phone, website and address ready for a CRM.
- **Screen vendors and suppliers** before you sign: BBB rating, accreditation,
  how many complaints closed in the last 3 years and last 12 months, years in
  business, legal entity type and whether the business is still trading.
- **Enrich a CRM** you already have: match on name + city and append rating,
  complaints, years in business and owners.
- **Track a market**: run it on a schedule and watch ratings, accreditation and
  complaint counts move.
- **Feed an AI agent or a RAG index** with structured business-trust data instead
  of scraped HTML.

***

### Fill rates — measured, with their sample size

Nothing is promised here without a number next to it. Listing fields were measured
on **1,411 businesses**; profile fields on **120 profiles sampled from BBB's own
sitemap**.

| Field | Fill rate |
|---|---|
| name, city, state, ZIP, category, coordinates, ids, profile URL | **100%** |
| rating, accreditation, complaints closed (3y and 12m), reviews total, average stars | **100%** |
| years in business, legal entity type, BBB file opened, local BBB, every location | **100%** |
| `categories` — every category BBB assigns, not just the primary one | **100%** |
| accepts quote requests · has service area | **100%** |
| phone | **97.5%** |
| address | **87.5%** |
| business started | **82.5%** |
| service areas (counties/ZIPs served) | **73%** |
| owners and officers | **71.7%** |
| website | **53.3%** |
| logo | **36.3%** |
| incorporation date | **35%** |
| accreditation date | **22.5%** |
| alternate trading names | **18.3%** |
| products and services · business description | **14.2% · 11.7%** |

Four fields BBB returns are empty in **0%** of the sample (`score`,
`outOfBusinessStatus`, `serviceAreaDescription`, `accreditationStatus`). They are
**not** in the output, because a field that is always empty is not a feature.

***

### 🔴 The thing nobody tells you about BBB data

**BBB's search box is not BBB's directory.** Measured: businesses returned by
BBB's own search are **96.7% accredited** and **90.8% rated A+**. A sample taken
from BBB's full profile sitemap is **0% accredited**, **47.5% A+ and 45% not
rated**.

That is the difference between BBB's paying shop window and the actual market. A
dataset built only from the search box will tell you every plumber in town is
A+ accredited. This actor reads **BBB's own sitemaps — 575 files of 10,000
profiles each, about 5.75 million businesses** — so it can hand you the rest of
the directory too.

If you want only the accredited ones, that is what `onlyAccredited` is for, and
it is a real filter: on Austin plumbers it goes from **2,409 businesses to 505**.

***

### Example runs

**1. BBB plumbers in Austin, TX**

```json
{ "searchTerms": ["plumber"], "locations": ["Austin, TX"], "maxItems": 500 }
```

**2. BBB Accredited roofing contractors in Chicago, IL**

```json
{ "searchTerms": ["roofing contractors"], "locations": ["Chicago, IL"], "onlyAccredited": true, "maxItems": 500 }
```

**3. HVAC contractors across Texas, with complaints and ratings**

```json
{ "searchTerms": ["air conditioning contractors"], "states": ["TX"], "maxItems": 2000 }
```

**4. General contractors in New York, NY**

```json
{ "searchTerms": ["general contractor"], "locations": ["New York, NY"], "maxItems": 1000 }
```

**5. Dentists in Phoenix, AZ with complaint history**

```json
{ "searchTerms": ["dentist"], "locations": ["Phoenix, AZ"], "fetchProfiles": true, "maxItems": 500 }
```

**6. Law firms in Los Angeles, CA**

```json
{ "searchTerms": ["lawyers"], "locations": ["Los Angeles, CA"], "maxItems": 1000 }
```

**7. A-rated moving companies in Florida**

```json
{ "mode": "search", "searchTerms": ["moving companies"], "states": ["FL"], "ratings": ["A"], "maxItems": 500 }
```

**8. Auto repair shops in Houston, TX**

```json
{ "searchTerms": ["auto repair"], "locations": ["Houston, TX"], "maxItems": 1000 }
```

**9. Electricians in Atlanta, GA that accept quote requests**

```json
{ "mode": "search", "searchTerms": ["electrician"], "locations": ["Atlanta, GA"], "onlyQuoteRequests": true, "maxItems": 300 }
```

**10. Landscaping companies in Miami, FL**

```json
{ "searchTerms": ["landscaping"], "locations": ["Miami, FL"], "maxItems": 500 }
```

**11. Pest control businesses in Ontario, Canada**

```json
{ "searchTerms": ["pest control"], "states": ["ON"], "country": "CAN", "maxItems": 500 }
```

**12. Restaurants in Philadelphia, PA**

```json
{ "searchTerms": ["restaurants"], "locations": ["Philadelphia, PA"], "maxItems": 1000 }
```

**13. Roofers in Dallas, TX rated B or lower**

```json
{ "mode": "search", "searchTerms": ["roofing contractors"], "locations": ["Dallas, TX"], "ratings": ["B", "C", "D", "F"], "maxItems": 500 }
```

**14. A slice of the whole California business directory**

```json
{ "mode": "sitemap", "states": ["CA"], "maxItems": 2000 }
```

**15. Home improvement contractors in Seattle, WA + category tree**

```json
{ "searchTerms": ["home improvement"], "locations": ["Seattle, WA"], "includeCategories": true, "maxItems": 500 }
```

***

### Pricing

Pay per event. You are charged for what is delivered, never for a page that
failed.

| Event | Price |
|---|---|
| Actor start | **$0.00001** — the platform minimum, which is a thousandth of a cent |
| Business delivered | **$0.003** |
| Full profile data added to a business | **$0.006** |
| BBB category taxonomy row | **$0.00001** — the platform minimum |

Apify does not allow an event priced at zero, so the two events that should be
free are set to the lowest price the platform accepts. A run of 10,000
businesses pays **$0.1** in start and taxonomy events — rounding, not pricing.

A run of 1,000 businesses with full profile data costs **$9**. Without profile
data, **$3**. Rows that error out are written to a separate `errors` dataset and
are never charged.

***

### Output

The default dataset has one row per business, with four ready-made views:
**Overview**, **Contact** (drop straight into a CRM), **Trust signals**
(rating, accreditation, complaints, reviews) and **Everything**. Export to JSON,
CSV, Excel or XML, or pull it from the Apify API.

Two extra datasets: `categories` (BBB's own taxonomy, when you ask for it) and
`errors` (what could not be fetched, and why).

***

### Questions people actually ask

**Does the Better Business Bureau have a public API?**
No. BBB does not publish a general business-data API, which is why most teams end
up scraping it. This actor gives you the same thing in practice: structured JSON
or CSV per business, callable from the Apify API or a schedule.

**Can I get BBB reviews and complaint text?**
No, and that is deliberate. You get the **counts and the average star rating** —
`reviews_total`, `review_average_stars`, `complaints_closed_3y`,
`complaints_closed_12m`, `complaints_total` — which is what you need to judge a
business. The **text** of reviews and complaints is written by identifiable
individuals, and this actor does not republish it.

**Can I get business email addresses?**
No. BBB deliberately obfuscates the email on the page to stop harvesting, and
this actor does not undo that. You get `has_email` (true/false), plus phone and
website.

**How many businesses can it return?**
BBB publishes about **5.75 million profiles** in its sitemaps, and this actor can
reach them. A single run is capped at 50,000 rows, and at whatever you set in
`maxItems`.

**How fast is it?**
Measured on the platform: **500 businesses per minute** at concurrency 6, with
0 blocks and 0 timeouts across 1,007 requests.

**Which cities and states does it cover?**
Every US state and Canadian province BBB lists. Filter with `locations`
(`"Austin, TX"`, `"Chicago, IL"`, `"New York, NY"`…), with `states`
(`"TX"`, `"CA"`, `"ON"`…), or leave both empty to sweep the directory.

**Does `"Miami, FL"` mean only businesses inside Miami?**
Usually yes, but not always. The actor reads BBB's own city listing pages, which
are strictly that city. When a trade has no listing page on BBB — `landscaping`
and `air conditioning contractors` are real examples — it falls back to BBB's
search, and **BBB's search covers the surrounding metro area**, so you may see
nearby towns. Every row carries `city`, `state` and coordinates, so filtering
further is one step on your side.

**Can I get only BBB Accredited Businesses?**
Yes — `onlyAccredited: true`. It uses BBB's own accredited listings rather than a
filter parameter that BBB ignores, so the count really does change.

**Does it need a login, a proxy or a browser?**
None of the three. It makes plain HTTP requests to pages BBB serves publicly.

***

### Notes

- **Not affiliated with, endorsed by, or connected to the Better Business
  Bureau or the International Association of Better Business Bureaus.** BBB and
  BBB Accredited Business are their trademarks, used here only to say what the
  data is.
- Only publicly visible information is collected. No login, no session cookies,
  no CAPTCHA solving.
- Business profiles include **names of owners and officers** as BBB publishes
  them — people acting in a professional capacity. In the EU/UK that is personal
  data and it is your responsibility to have a lawful basis for processing it.
- Customer reviews and complaint texts are **not** collected. Only their counts.
- Removal requests: **privacy@actorstack.dev**, answered within 30 days.
- Data reflects BBB at the time of the run. Every row carries `fetched_at` and
  `from_cache`, so you always know how old it is.

# Actor input Schema

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

What kind of business to look for, e.g. `plumber`, `roofing`, `dentist`, `auto repair`. Matches BBB's own category names.

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

Cities as `City, ST`, e.g. `Austin, TX` or `Chicago, IL`. Leave empty to cover whole states, or everything.

## `states` (type: `array`):

Two-letter codes, e.g. `TX`, `CA`, `ON`. Used when you want a whole state rather than single cities.

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

BBB covers the United States and Canada.

## `maxItems` (type: `integer`):

How many businesses to return. You are charged per business delivered — never for rows that failed. Hard cap: 50,000 per run.

## `onlyAccredited` (type: `boolean`):

Restricts the run to accredited businesses, using BBB's own accredited listings. This is a real filter: measured on Austin plumbers it goes from 2,409 businesses to 505. (BBB's search API has an `accredited` parameter that is silently ignored — this actor does not use it.)

## `fetchProfiles` (type: `boolean`):

Fetches each business's BBB profile page and adds ~35 fields the listing does not have: website, years in business, legal form, incorporation dates, complaint and review counts, owners and officers, alternate names, every location. Costs one extra request per business and is charged as a separate event.

## `mode` (type: `string`):

`auto` picks for you and is almost always right. `sitemap` sweeps BBB's own profile sitemaps — 575 files of 10,000 profiles, the only path that reaches the whole directory. `category` reads BBB's category pages, which return 15 businesses per page and up to 225 per query. `search` uses BBB's search API, which allows rating filters and sort orders.

## `ratings` (type: `array`):

BBB accepts single letters only: A, B, C, D or F, and a letter covers its family (A also returns A+ and A−). It does NOT accept `A+`: asking for `A+` returns zero results even though 90.8% of businesses in BBB's search are rated A+. Leave empty for every rating.

## `onlyQuoteRequests` (type: `boolean`):

Keeps only businesses that take quote requests through BBB — a commercial-intent signal. Measured to change results.

## `sortOrders` (type: `array`):

BBB caps every query at 15 pages × 15 results = 225. A-Z and Z-A sweep the same set from opposite ends, so running both returns up to 450 distinct businesses per query instead of 225.

## `includeCategories` (type: `boolean`):

Writes BBB's own category tree (code, name, family, where it was seen) to a separate `categories` dataset. It travels in the same responses, so it costs no extra requests and is charged at $0.

## `maxConcurrency` (type: `integer`):

Kept deliberately low. BBB served 20 consecutive requests without a single block in testing, and this actor starts well under that ceiling rather than hunting for it.

## Actor input object example

```json
{
  "searchTerms": [
    "plumber"
  ],
  "locations": [
    "Austin, TX"
  ],
  "states": [],
  "country": "USA",
  "maxItems": 100,
  "onlyAccredited": false,
  "fetchProfiles": true,
  "mode": "auto",
  "ratings": [],
  "onlyQuoteRequests": false,
  "sortOrders": [
    "AToZ",
    "ZToA"
  ],
  "includeCategories": false,
  "maxConcurrency": 4
}
```

# Actor output Schema

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

Every business delivered by this run.

## `datasetPreview` (type: `string`):

Open the dataset with its views.

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

Pages that could not be fetched. Never charged.

# 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"
    ],
    "locations": [
        "Austin, TX"
    ],
    "country": "USA",
    "maxItems": 100,
    "fetchProfiles": true,
    "mode": "auto",
    "sortOrders": [
        "AToZ",
        "ZToA"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("oswaldocarabano/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 = {
    "searchTerms": ["plumber"],
    "locations": ["Austin, TX"],
    "country": "USA",
    "maxItems": 100,
    "fetchProfiles": True,
    "mode": "auto",
    "sortOrders": [
        "AToZ",
        "ZToA",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("oswaldocarabano/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 '{
  "searchTerms": [
    "plumber"
  ],
  "locations": [
    "Austin, TX"
  ],
  "country": "USA",
  "maxItems": 100,
  "fetchProfiles": true,
  "mode": "auto",
  "sortOrders": [
    "AToZ",
    "ZToA"
  ]
}' |
apify call oswaldocarabano/bbb-business-scraper --silent --output-dataset

```

## MCP server setup

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