# BBB Businesses Scraper (`nice_dev/bbb-businesses-scraper`) Actor

BBB scraper for the Better Business Bureau (bbb.org): businesses in the US and Canada with name, BBB rating, accreditation, phone, address and category, plus optional full profile, reviews, complaints and the email found on the company website. Export to JSON, CSV or Excel.

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

## Pricing

from $0.62 / 1,000 businesses

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

### 🏢 What is BBB Businesses Scraper?

**BBB Businesses Scraper** extracts **businesses from [BBB, the Better Business Bureau](https://www.bbb.org)** in the United States and Canada: **name, BBB rating, accreditation, phone, address and category**, and, as options, the **full profile** (owners, email, website, licenses, opening hours), every **customer review and complaint** with the whole exchange between customer and business, and the **email found on the company's own website**. It works as a **BBB API alternative**: run it on a schedule, call it from your code, or plug it into Make, Zapier or n8n.

Type a **keyword** (`plumber`) and a **location** (`Austin, TX`), or paste any BBB URL, click **Start**, and download the businesses in JSON, CSV or Excel. No login, nothing to set up. BBB's own search stops at 225 businesses per search: this Actor goes past that limit, and its **Full directory** mode lists every business of a state or a city.
It is fast: on the Apify platform, **1,000 businesses from the search in under 5 minutes** (about 200 a minute), about 85 a minute in **Full directory** mode, and about 40 a minute with the **full profile** of each business.

### 📋 What data can you extract from BBB?

One item per business, 87 fields:

| Category | What you get |
| --- | --- |
| 🏷️ **Business** | name, BBB link, BBB ids, categories — `Calixto Plumbing`, `Plumber` |
| ⭐ **BBB rating and accreditation** | letter grade and score, accredited or not, accreditation date, charity seal, out-of-business flag — `A+` |
| 📍 **Address and map** | street, city, state, ZIP, country, GPS position, service areas, local BBB office — `Austin, TX 78753` |
| 📞 **Phone, website and email** | phones (also in international format), website, social media links, the email BBB shows — `(512) 945-3143` |
| ✉️ **Email from the company website** | the addresses found on the company's own website (option), with the page they came from — `service@calixtoplumbing.com` |
| 👤 **Owners and licenses** | owners and managers with their title, licenses with number, agency and status — `Juan Calixto, Owner` |
| 🕒 **Opening hours, dates and size** | opening hours per day, years in business, start and incorporation dates, employees, legal form, description, payment methods |
| 📊 **Reputation figures** | review count and average stars, complaint counts by type and status, complaints closed in the last 12 months, complaints of the last 3 years, alerts BBB displays |
| 💬 **Customer reviews** | stars, reviewer first name and initial, date, full text, the business reply and the rest of the exchange (option) |
| 📣 **Customer complaints** | type, status, date, full text and the whole thread of replies (option) |
| 🏬 **Other locations** | the other establishments of a multi-location business, up to 100 (option) |
| 🔎 **Search details** | the keyword and location that found the business, the BBB page of the search |

Every field, with an example, is listed in the **Output** section below.

Fields that BBB shows on the business page only (owners, licenses, hours, website, email, dates…) are filled when **Full business profile** is ticked, and are `null` or empty otherwise. Reviews, complaints and the website email have their own checkbox. Every option is **off by default** and charged on its own: you pay only for what you tick.

### ✅ Why use BBB Businesses Scraper?

- 📈 **Past the 225-result limit**: BBB's search shows at most 225 businesses per search. Go past it with one checkbox (on by default), or take **every business of a state or a city** (BBB lists more than 5 million), accredited or not, rated or not.
- 💬 **Complete reviews and complaints**: every page, with the business reply and the full thread. Keep only the recent ones, one status or one type of complaint.
- 🧾 **The whole profile in one run**: owners with their title, licenses, opening hours, social media, years in business, rating reasons and the other locations of a chain.
- ✉️ **The email of the company, not only BBB's**: tick one option to read the contact pages of the company's own website.
- 🎯 **Type the keyword, or paste any BBB URL**: a search, a category page or one business.
- 🔔 **Monitoring built in**: tick **Only new businesses**, schedule the Actor, and each run returns (and charges) only what it has never delivered before.
- 🧩 **Nothing twice**: pagination is automatic, a business found by several searches is saved once.
- 🔌 API, scheduling, monitoring, integrations (Make, Zapier, n8n, Google Sheets…) and JSON/CSV/Excel export via the Apify platform.

### 🚀 How to scrape BBB

1. Create a free Apify account.
2. Open **BBB Businesses Scraper**, type a **Search keyword** (e.g. `plumber`) and a **Location** (e.g. `Austin, TX`).
3. Or paste your own bbb.org URLs into **Start URLs**: a search results page (the filters set on the site are kept), a category page or a business page.
4. Tick the options you need (**Full business profile**, **Customer reviews**, **Customer complaints**, **Email from the company website**) and set the filters.
5. Set **Max businesses** (100 by default, 0 = no limit) — and **Max businesses per search** when you run several searches — then click **Start**.
6. Download the dataset in JSON, CSV, Excel or via API.

### 💰 How much does it cost to scrape BBB?

This Actor uses **pay per event** pricing: you pay for each business saved, plus a small fee at each run start. The options are charged only for what they deliver, and only when ticked. Prices are in US dollars **per 1,000 events** and depend on your Apify plan; a paid plan pays less. Platform usage (compute, proxy) is included in the price.

| You pay for | Event | Free | Bronze | Silver | Gold |
| --- | --- | --- | --- | --- | --- |
| Each business saved (the row, without the options below) | `apify-default-dataset-item` | **$0.89** | $0.79 | $0.71 | $0.62 |
| Each business delivered with its full profile (**Full business profile**) | `business-details` | **$0.69** | $0.63 | $0.54 | $0.45 |
| Each customer review delivered (**Customer reviews**) | `review` | **$1.79** | $1.78 | $1.77 | $1.76 |
| Each customer complaint delivered (**Customer complaints**) | `complaint` | **$8.99** | $8.59 | $8.09 | $7.69 |
| Each business delivered with an email address that BBB does not show, found on its own website (**Email from the company website**): one event per business, however many addresses | `email-enriched` | **$3.39** | $1.79 | $1.29 | $0.86 |
| Each business a filter drops after the search result was read (a business you keep pays its own price, never both) | `filter-check` | **$0.071** | $0.066 | $0.061 | $0.056 |
| Each business a filter drops after its profile page was read | `detail-filter-check` | **$0.42** | $0.41 | $0.40 | $0.39 |

Plus **$0.01 per run start** (`apify-actor-start`), on every plan.

Examples, run start included:

- **100 businesses, no option**: $0.10 on Free ($0.089 + $0.01), $0.07 on Gold ($0.062 + $0.01).
- **100 businesses with the full profile, 20 reviews each (2,000 reviews) and an email from the website for 75 of them**: $4.00 on Free (businesses $0.089 + profiles $0.069 + reviews $3.58 + emails $0.254 + run start $0.01), $3.70 on Gold (businesses $0.062 + profiles $0.045 + reviews $3.52 + emails $0.065 + run start $0.01).

Reviews and complaints are charged one by one, as delivered: **Max reviews per business** and **Max complaints per business** (50 by default, 0 = all of them) are your cap on the bill. A business with no review costs nothing for reviews. Businesses already delivered to an **Only new businesses** run are never charged twice, and a business found by several searches is charged once.

### ⚙️ Input

```json
{
    "query": "plumber",
    "location": "Austin, TX",
    "maxItems": 200
}
```

Several searches, a cap per search, only the best-rated accredited businesses, only the ones not delivered before:

```json
{
    "searchQueries": ["plumber", "roofing"],
    "locations": ["Austin, TX", "Dallas, TX"],
    "maxItemsPerQuery": 50,
    "minRating": "A",
    "accreditedOnly": true,
    "onlyNew": true,
    "stateKey": "texas-trades"
}
```

Full profile, reviews of the last 30 days, resolved complaints and the email found on the company website:

```json
{
    "query": "plumber",
    "location": "Austin, TX",
    "maxItems": 20,
    "includeDetails": true,
    "includeReviews": true,
    "maxReviewsPerBusiness": 20,
    "includeComplaints": true,
    "complaintStatus": ["resolved"],
    "postedAfter": "30 days",
    "enrichEmails": true
}
```

Every plumber in Texas, from BBB's full directory:

```json
{
    "mode": "directory",
    "query": "plumber",
    "location": "TX",
    "maxItems": 1000
}
```

Or with your own URLs:

```json
{
    "startUrls": [
        { "url": "https://www.bbb.org/search?find_text=plumber&find_loc=Austin%2C%20TX" },
        { "url": "https://www.bbb.org/us/tx/austin/category/plumber" },
        { "url": "https://www.bbb.org/us/tx/austin/profile/plumber/calixto-plumbing-0825-1000223803" }
    ],
    "maxItems": 500
}
```

| Field | Notes |
| --- | --- |
| `mode` | `search` (default): the BBB search, keyword × location. `directory`: every business BBB lists in the chosen states or cities, accredited or not; the location is then a state (`TX`) or a city (`Austin, TX`), the keyword filters on the category (`plumber`). |
| `query`, `searchQueries` | Category or business name, as typed on bbb.org (`plumber`, `roofing`, `Roto-Rooter`); `searchQueries` adds more keywords (one search each). Ignored when `startUrls` is set. |
| `location`, `locations` | City and state or province (`Austin, TX`, `Toronto, ON`), ZIP or postal code (`78701`) or state (`TX`); `locations` adds more places. Every keyword is searched in every location (max 500 searches per run). BBB searches around the place: nearby cities come too. |
| `country` | `USA` or `CAN`: the country of the search. It is deduced from the location when the location says it (a province, a Canadian postal code). |
| `startUrls` | bbb.org search results (filters kept), category pages (`/us/tx/austin/category/plumber`) or business pages (`/us/tx/austin/profile/plumber/calixto-plumbing-0825-1000223803`). Max 1 000 URLs. When set, the search fields are ignored; filters, options, caps and monitoring still apply. |
| `maxItems` | Stop after this many businesses for the whole run (100 by default, `0` = no limit). |
| `maxItemsPerQuery` | Cap for EACH search (keyword × location, or start URL), so that the first search cannot use up the whole budget. `0` = no per-search cap. |
| `sort`, `maximizeCoverage` | Order of the BBB results: `Relevance`, `Distance`, `Rating`, `AToZ`, `ZToA`. Go past the 225-result limit (on by default): a search with more results is run again with the other sort orders, each rating grade and each sub-category, and every business is kept once. Off = the first 225 only. |
| `includeDetails` | **Full business profile**: opens the BBB page of each business for owners, email, website, licenses, hours, social media, dates, rating reasons and the other locations. Billed per profile delivered. |
| `includeReviews`, `maxReviewsPerBusiness`, `includeComplaints`, `maxComplaintsPerBusiness` | **Customer reviews** and **Customer complaints**: full text with the whole thread, newest first, billed per entry delivered. The two caps are per business (50 by default, 0 = all of them). Each business is held in memory with all its entries until it is saved, so the Actor sets the run's memory from the caps and refuses a run that has less before it starts: 256 MB up to 400 entries per business (reviews + complaints), 512 MB up to 1,200, 1 GB up to 2,500, 2 GB above (0 counts as 5,000 per option). With more than 16 businesses at a time (`maxConcurrency`) the entries count double: above 2,500 the run is refused, lower the caps or the concurrency. |
| `complaintStatus` | Only the complaints with one of these statuses: `answered`, `resolved`. Empty = all. Needs **Customer complaints**. |
| `complaintTypes` | Only the complaints of one of these types: `serviceorrepair`, `billingissues`, `productissues`, `salesadvertising`, `customerservice`, `order`. Empty = all. Needs **Customer complaints**. |
| `enrichEmails` | **Email from the company website**: visits the company's own website (home, contact and legal pages, 3 pages at most) for its email addresses. Needs **Full business profile**, because the website address comes from it. Billed once per business where an address BBB does not show is found. |
| `minRating`, `accreditedOnly` | Keep the businesses rated at least this grade (`A+` best, `F` worst; businesses BBB does not rate are dropped), and only the BBB accredited ones. |
| `minYearsInBusiness` | Keep the businesses open for at least this many years (`10`). Needs **Full business profile**; a business with no year shown is dropped. |
| `distance` | Search mode: keep the businesses within this many miles of the location: `5`, `10`, `25`, `50` or `100`. |
| `onlyQuoteRequests`, `requirePhone`, `requireWebsite`, `requireEmail` | Keep only the businesses that take quote requests through BBB, that show a phone number, that have a website, or that show an email on their BBB page. The last two need **Full business profile**; the email check reads BBB's email, the company website is read afterwards. |
| `postedAfter`, `postedBefore` | Reviews and complaints date range: `2026-09-01`, or a period before now (`7 days`, `2 weeks`, `1 month`, `24 hours`). It applies to the review and complaint lists, not to the businesses. Needs **Customer reviews** or **Customer complaints**. |
| `excludeKeywords` | Drop the businesses whose name or category contains one of these words (`franchise`; case and accents ignored). |
| `onlyNew`, `stateKey`, `resetState` | Monitoring: only the businesses never delivered under this memory key (`austin-plumbers`); `resetState` forgets the memory. |
| Advanced | `proxyConfiguration` (Apify proxy by default, included in the price; the residential proxy is not available), `maxConcurrency`, `maxRequestsPerMinute`, `minRequestIntervalMs`, `maxRequestRetries`, `debugLog`. |

### 📦 Output

Shortened example (a business with the full profile, one review and the email option; the real item has the 87 fields below, `null` where BBB shows nothing):

```json
{
    "id": "0825-1000223803",
    "businessId": "1000223803",
    "bbbId": "0825",
    "url": "https://www.bbb.org/us/tx/austin/profile/plumber/calixto-plumbing-0825-1000223803",
    "name": "Calixto Plumbing",
    "phone": "(512) 945-3143",
    "phoneE164": "+15129453143",
    "address": "8410 Georgian Dr # B, Austin, TX 78753-6003",
    "city": "Austin",
    "state": "TX",
    "postalCode": "78753-6003",
    "country": "USA",
    "bbbRating": "A+",
    "isAccredited": true,
    "primaryCategory": "Plumber",
    "categories": ["Plumber", "Plumbing Renovation"],
    "categoryIds": ["10113-000", "10113-300"],
    "serviceAreas": ["Travis County, TX"],
    "alternateNames": [],
    "website": "https://calixtoplumbing.com",
    "additionalWebsites": [],
    "email": "info@calixtoplumbing.com",
    "additionalEmails": [],
    "websiteEmails": ["service@calixtoplumbing.com"],
    "emailSource": "https://calixtoplumbing.com/contact",
    "additionalPhones": [],
    "faxNumbers": [],
    "contacts": [{ "name": "Juan Calixto", "title": "Owner", "isPrincipal": true }],
    "ownerName": "Juan Calixto",
    "licenses": [{ "number": "M-12345", "agency": "Texas State Board of Plumbing Examiners", "status": "Active" }],
    "openingHours": [{ "day": "Monday", "open": "08:00", "close": "17:00", "note": null }],
    "paymentMethods": ["Visa", "Check"],
    "yearsInBusiness": 18,
    "ratingReasons": ["Length of time business has been operating"],
    "alerts": [],
    "reviewCount": 29,
    "complaintCount": 3,
    "branches": [],
    "reviews": [
        {
            "id": "0825_1000223803_162637",
            "rating": 5,
            "reviewerName": "Christina N",
            "date": "2026-09-09",
            "text": "Quick and fair.",
            "businessResponse": "Thank you!",
            "businessResponseDate": "2026-09-10",
            "thread": [{ "from": "business", "title": "Calixto Plumbing Response", "date": "2026-09-10", "text": "Thank you!" }]
        }
    ],
    "complaints": [],
    "source": "search",
    "searchQuery": "plumber",
    "searchLocation": "Austin, TX",
    "scrapedAt": "2026-10-02T12:00:00.000Z"
}
```

You can download the dataset in various formats such as JSON, HTML, CSV or Excel.

#### All 87 fields

A field a business does not have is `null`; a list it does not have is `[]`. Fields marked **profile** need **Full business profile**, **reviews** and **complaints** their own checkbox, **email** the website email option.

| Fields | What you get |
| --- | --- |
| `id`, `businessId`, `bbbId`, `url`, `name` | `0825-1000223803` (the stable key of the business), the two numbers it is made of (the business, and the local BBB office), the BBB page, `Calixto Plumbing` |
| `phone`, `phoneE164` | `(512) 945-3143` as BBB shows it, `+15129453143` ready for a dialer or a CRM |
| `address`, `street`, `city`, `state`, `postalCode`, `country` | the address in one line and in parts; `USA` or `CAN` |
| `latitude`, `longitude` | GPS position, to 6 decimals (search results, or **Full business profile** for a business read from the directory or a pasted URL) |
| `bbbRating`, `ratingScore` | letter grade `A+` to `F`, or `NR` (not rated), as the profile prints it; the score behind it, 0-100 (search results only: `null` from the directory and for a pasted business URL) |
| `isAccredited`, `accreditedSince`, `accreditationText`, `accreditationRevokedAt` | accredited or not, since when (**profile**; none once no longer accredited, as on the page), the statement shown on the page (**profile**), the date BBB revoked it (**profile**) |
| `isCharity`, `charitySeal`, `accreditedCharity`, `outOfBusiness` | charity flags and the out-of-business mark |
| `primaryCategory`, `categories`, `categoryIds` | main BBB category, all categories, their BBB ids |
| `serviceAreas`, `hasServiceArea`, `distanceMiles` | areas served, whether the business declares any, distance from the searched place in miles (search results with a location) |
| `logoUrl`, `localBbb`, `localBbbUrl`, `locationUrl` | the business logo image, the local BBB office and its page (**profile**), the BBB page of this exact address for a multi-location business (search results) |
| `acceptsQuoteRequests`, `requestAQuoteUrl`, `leaveReviewUrl`, `reviewsUrl`, `complaintsUrl` | quote requests through BBB (`null` = unknown: directory or pasted URL without the profile) and the BBB pages for a quote, a review, the reviews and the complaints |
| `alternateNames`, `website`, `websiteDomain`, `additionalWebsites`, `socialMedia` | other names (DBA, former names), website and its domain, other websites, social media links (**profile**) |
| `email`, `additionalEmails` | the email BBB shows, and the other ones (**profile**) |
| `websiteEmails`, `emailSource` | the addresses found on the company's own website, and the page of the first one (**email**) |
| `additionalPhones`, `faxNumbers` | other phone numbers, fax numbers (**profile**) |
| `contacts`, `ownerName`, `ownerTitle` | owners and managers with their title, and the first principal one (**profile**) |
| `licenses` | licenses with number, agency, agency phone and link, status, expiration, type (**profile**) |
| `openingHours` | opening hours per day, with BBB's own note when it writes text instead of hours (**profile**) |
| `description`, `productsAndServices`, `paymentMethods`, `entityType`, `numberOfEmployees`, `yearsInBusiness` | what the business says about itself, how it is organised and how long it has been open (**profile**) |
| `businessStarted`, `incorporatedDate`, `bbbFileOpened`, `businessStartedLocally`, `isClaimed`, `isHeadquarters` | key dates (`YYYY-MM-DD`), whether the owner claimed the BBB page, whether this is the headquarters (**profile**) |
| `ratingReasons`, `alerts` | the reasons BBB gives for its grade, the alerts it displays (**profile**) |
| `reviewCount`, `averageReviewRating` | customer reviews on BBB and their average stars (the average: **profile**) |
| `complaintCount`, `complaintsClosedLast12Months`, `complaintsClosedLast3Years`, `hiddenComplaints` | complaints BBB counts, as its page's total (hidden ones included), the ones closed in the last 12 months (**profile**), the complaints BBB counts over the last 3 years, the profile's "total complaints in the last 3 years" (**profile**), and the ones BBB counts but does not display (**complaints**) |
| `complaintsByType`, `complaintsByStatus` | number of complaints per type and per status (**complaints**) |
| `isMultiLocation`, `branches` | several locations or not, and the other locations with name, phone, address, rating and BBB page, up to 100 (**profile**) |
| `reviews` | the customer reviews, newest first, with the business reply and the thread (**reviews**) |
| `complaints` | the customer complaints, newest first, with type, status and the thread (**complaints**) |
| `source`, `searchQuery`, `searchLocation`, `searchUrl`, `scrapedAt` | how the business was found (`search`, `directory` or `url`), the keyword and location, the start URL, ISO timestamp |

### 💡 Tips

#### How to get more results

BBB's search stops at 225 businesses per search. Keep **Go past the 225-result limit** on (it is by default) and set **Max businesses** to `0`: a search with more results is run again with the other sort orders, each rating grade and each sub-category. For a whole state or city, use **Full directory** (`mode`: `directory`) with `TX` or `Austin, TX` as the location and a keyword for the category: no limit of 225, accredited or not.

#### How to reduce costs

The price is per business, so the levers are **Max businesses**, **Max businesses per search**, the filters and **Only new businesses** for recurring runs (you never pay twice for the same business). Tick **Full business profile**, **Customer reviews**, **Customer complaints** and **Email from the company website** only when you use what they deliver, and cap reviews and complaints per business: each review or complaint is an event of its own.

#### Reviews and complaints

Tick **Customer reviews** and / or **Customer complaints** to get every entry with the whole exchange between customer and business. Narrow them with **Reviews and complaints after** (a date, or a period like `7 days`), **Complaint status** and **Complaint types**. To follow what happened to businesses you already know, schedule a run with **Reviews and complaints after** set to your schedule period.

#### Several searches in one run

Fill `searchQueries` and / or `locations`: the Actor runs one search per keyword × location (3 keywords × 4 cities = 12 searches, up to 500 per run). The single `query` and `location` fields still work and are added to the lists. A business found by several searches is saved — and charged — once. Set `maxItemsPerQuery` to give every search its own cap: without it the first searches can use up the whole `maxItems` budget. You can also paste several search URLs into `startUrls`: each one is a search of its own, with the same cap (the same search pasted at page 1 and at page 3 is two searches: a cap of 5 gives 5 + 5 businesses, none twice).

#### Monitoring: only the new businesses

Tick **Only new businesses** (`onlyNew`) and schedule the Actor. The first run returns everything; each later run skips the businesses already delivered: they are not saved, not charged, and their page is not even opened. The memory lives in a named key-value store of your account (`bbb-businesses-scraper-seen`, up to 150,000 businesses per key) and is only updated with businesses that really reached the dataset, so a failed run never hides anything. Give each schedule its own `stateKey` (two schedules sharing a key would hide each other's businesses), and tick `resetState` once to start over. BBB has no sort by date, so a monitoring run reads its whole search each time, and in directory mode every file of the state or category: it never stops early on a stretch of pages with nothing new, because a new business can be anywhere in them. It only saves, and charges, the new businesses. With Only new businesses, a business your filters drop is checked (and charged) once; the next runs with the same filters skip it for free. Change a filter, or tick Reset the memory, to check them again.

### 🔌 Integrations and API

Call the Actor via the Apify API, the JavaScript or Python clients, or connect it with integrations and webhooks (Make, Zapier, n8n, Google Sheets, Slack, Airtable…). The dataset can be fetched as JSON or CSV from any tool.

### 🤖 Use with AI agents (MCP)

AI agents (Claude, ChatGPT, Cursor…) can find and run this Actor through the [Apify MCP server](https://mcp.apify.com), billed to their Apify account like any run. It returns one item per BBB business. Actor id: `nice_dev/bbb-businesses-scraper`; MCP server with this Actor only: `https://mcp.apify.com/?tools=fetch-actor-details,nice_dev/bbb-businesses-scraper`.

Smallest input, for a cheap first call:

```json
{
    "query": "plumber",
    "location": "Austin, TX",
    "maxItems": 10
}
```

Key output fields: `name`, `url`, `bbbRating`, `isAccredited`, `phone`, `address`, `primaryCategory` and, with the options, `contacts`, `email`, `reviews` and `complaints`.

Cost: $0.89 per 1,000 businesses plus $0.01 per run start ($0.62 per 1,000 on the Gold plan); the full profile, reviews, complaints, the website email and filter checks cost extra, see the pricing section above. Cap each call with `maxItems` and, through the API, with the run option `maxTotalChargeUsd`.

### ❓ FAQ

#### Is it legal to scrape BBB?

The Actor only reads what BBB shows publicly to any anonymous visitor. It logs in to nothing. Results can contain personal data, which is protected by GDPR: owners and managers are named with their title, reviews are signed with a first name and an initial, and a business email can be a person's address. Do not store it without a legitimate reason. You are responsible for using the data in compliance with BBB's Terms of Use and applicable law. This Actor is not affiliated with BBB or the Better Business Bureau.

#### Where do the email addresses come from?

The email BBB shows comes from the business page (**Full business profile**). With **Email from the company website** on, the Actor also reads the pages each company publishes on its own website (home page, contact page, legal notice) and returns the business contact addresses shown there — what any visitor sees. It does not log in, guess addresses, read contact forms or bypass any protection. Some of these addresses are personal data under GDPR (e.g. `firstname.lastname@company.com`): you are responsible for having a lawful basis before using them, for informing the people concerned and for honouring opt-outs. Rules for B2B email prospecting differ by country (opt-out in some, prior consent in others).

#### Does it need a login or a proxy?

No login. The proxy is included in the price: leave the default setting (the residential proxy is not available). A request the site turns away is retried at once on a new proxy session, up to 10 times on top of the retries (without a proxy, after a pause of 5 seconds, doubled at each retry up to 150 seconds).

#### Is the data safe to open in Excel or to show on a web page?

Names, descriptions, reviews and complaints are the businesses' and customers' own words, copied as they are. A text can begin with `-`, `+`, `=` or `@`: Excel and Google Sheets may read such a cell of a CSV file as a formula or as a number. The Actor leaves the text as it is, so that the JSON and the API give the real value: when you open a CSV, import these columns as text. On a web page, escape every field like any text written by a stranger.

#### Known limitations

- BBB's search shows at most 225 businesses per search; **Go past the 225-result limit** and **Full directory** are the ways past it. A location is a centre: a search for `Austin, TX` also brings nearby cities.
- A search needs a keyword; without a location it covers the whole country. In **Full directory** mode the location is a state or a city, and the filters that BBB's search offers (**Distance**, **Accepts quote requests**) are not available.
- The fields of **Full business profile** are `null` or empty without it. **Reviews and complaints after** applies to reviews and complaints, not to the businesses, and BBB has no filter on the number of stars.
- **Only new businesses** remembers business ids, not their content: a business whose rating changed is not returned again, and neither are the new reviews of a business already delivered (use **Reviews and complaints after** for that).
- **Email from the company website** reads home, contact and legal pages (3 pages at most): a website that builds its addresses with scripts, shows them as images or hides them behind a form gives none. The email BBB shows stays in its own field.
- Two runs sharing the same `stateKey` at the same time may both return the same new business.
- A business read from **Full directory**, a category page or a pasted business URL comes with what BBB gives for one business, not with a search result: `ratingScore`, `distanceMiles`, `locationUrl`, `serviceAreas`, `hasServiceArea` and the charity flags (`isCharity`, `charitySeal`, `accreditedCharity`) are `null` or empty there, and so are the position, `localBbb` and the list of categories unless **Full business profile** is ticked (`primaryCategory` is then the category of its URL).
- A search result can list a fax number among the phones: with **Full business profile**, faxes are in `faxNumbers` and not in `additionalPhones`.
- A search can list a business under another of its names (a trade name, even the name of a related business): with **Full business profile**, `name` is the one its profile shows and the others are in `alternateNames`. A branch of a chain found in **Full directory** or by its URL has no name nor grade where BBB lists it: without **Full business profile** its name is spelled from its URL and `bbbRating` is `null` — and so are the `name` and `bbbRating` of such branches in `branches`.
- Dates (`businessStarted`, `incorporatedDate`, `bbbFileOpened`, `accreditedSince`…) are the days BBB records: its profile page can print the day before for a date recorded at midnight UTC (a time zone effect).

**A run that reaches its timeout** stops itself about 45 seconds before it: no new page is asked, what it read is saved and, with `onlyNew`, remembered, and the run ends *Succeeded* with "Stopped before the run's timeout". Resurrect it to go on from there, or give the next run a longer timeout (Run options).

**A run the platform stops without warning** (out of memory)

- Resurrect it: it goes on from where it stood at most a minute before the stop. What it had read since is read again, and the businesses already saved are skipped: none is delivered or charged twice, and `maxItems` still counts them.
- With `onlyNew`, the memory is saved right after the businesses are written (once every 2 seconds at most): a run left stopped for good only leaves out of it the businesses written in its last 2 seconds, which the next run may return once more; resurrect the stopped run and they join the memory.

#### Something doesn't work?

The last line of the log counts the businesses saved, filtered out and no longer on BBB (removed while the run was reading them), and the requests that failed after every retry. Those requests and the removed businesses are listed, with the reason, in the `FAILED_REQUESTS` record of the run's key-value store. A run that saved nothing and had failed requests fails, and its last message gives the cause (a search URL that does not exist says so, instead of "run it again").

If BBB changes its pages, you are told instead of paying for blank rows. A results page that counts businesses but gives none the Actor can read is an error (listed in `FAILED_REQUESTS`), never a quiet "No results". If the first 20 businesses read all lack their name, city, state, category or rating, the run saves nothing more, stops and fails, and its last message names the missing field: at most those first businesses are charged. A business that a filter drops counts among those 20. A run of fewer businesses (a scheduled `onlyNew` run that finds a few new ones, a small **Max businesses**) is checked at its end, from 5 businesses: its businesses are already saved, and the run fails with the missing field instead of ending green.

### 🛟 Support

Open an issue in the **Issues** tab with a link to your run: the run log and the `FAILED_REQUESTS` record of the key-value store show exactly which URLs failed and why.

# Actor input Schema

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

**Search** = the BBB search (keyword × location), like on bbb.org; past its 225-result cap when **Go past the 225-result cap** is on. **Full directory** = every business BBB lists in the chosen states / cities (accredited or not, rated or not), read from BBB's own business index: **Location(s)** are then `TX` or `Austin, TX` (no ZIP codes) and **Search keyword(s)** filter on the category (`plumber`). Start URLs, when given, override both.

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

bbb.org URLs: search results (`https://www.bbb.org/search?find_text=plumber&find_loc=Austin%2C%20TX`, the filters set on the site are kept), category pages (`/us/tx/austin/category/plumber`) or business profiles (`/us/tx/austin/profile/plumber/calixto-plumbing-0825-1000223803`). When this list is not empty, the search fields below are ignored; filters, options, caps and monitoring still apply. Max 1 000 URLs.

## `query` (type: `string`):

Category or business name, as typed on bbb.org (e.g. `plumber`, `roofing`, `Roto-Rooter`). In **Full directory** mode: words that the business category must contain (empty = all categories).

## `searchQueries` (type: `array`):

Several keywords in one run: one search per keyword (times each location below). Added to **Search keyword**; businesses found by several searches are saved once.

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

City and state or province (`Austin, TX`, `Toronto, ON`), ZIP / postal code (`78701`) or state (`TX`). BBB searches AROUND this place (nearby cities come too; use **Distance** to narrow). In **Full directory** mode: `TX` or `Austin, TX` only.

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

Several locations in one run: every keyword is searched in every location (3 keywords × 4 locations = 12 searches, max 500). Added to **Location**.

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

Country of the search: United States or Canada (BBB lists no other country).

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

Maximum number of businesses to save for the whole run (after deduplication and filters). 0 = no limit.

## `maxItemsPerQuery` (type: `integer`):

Cap for EACH search (keyword × location, or start URL), so that the first search cannot use up the whole **Max businesses** budget. 0 = no per-search cap.

## `maximizeCoverage` (type: `boolean`):

BBB shows at most 225 businesses per search (15 pages). On: when a search has more, it is re-run with the other sort orders, each rating grade and each sub-category BBB proposes, and every business is kept once. Off = the first 225 only (faster).

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

Order of the BBB results (matters when a search has more businesses than you collect). Not used in Full directory mode: the businesses come in the order of BBB's business index, by local BBB office — with a state or a city, the offices that list the most businesses there first.

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

Open each BBB profile page: owners and managers (name + title), email, website, extra phones, licenses (number, agency), opening hours, social media, years in business, legal form, employees, description, BBB rating reasons, review and complaint totals, branches of multi-location businesses (the first 100 locations BBB lists for each). Billed per business delivered with its profile.

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

Full text of the customer reviews (stars, first name + initial, date) with the business reply and the rest of the exchange, newest first. Billed per review delivered.

## `maxReviewsPerBusiness` (type: `integer`):

Newest reviews kept per business. 0 = all of them. The run's memory rises with the caps (256 MB up to 400 reviews + complaints per business, 512 MB up to 1,200, 1 GB up to 2,500, 2 GB above; 0 counts as 5,000) and a run started with less is refused.

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

Full text of the complaints filed with BBB: type (billing, service, product…), status (answered, resolved…), date, and the whole thread of business and customer replies, newest first. Billed per complaint delivered.

## `maxComplaintsPerBusiness` (type: `integer`):

Newest complaints kept per business. 0 = all of them. Counted with the reviews for the run's memory (see Max reviews per business); with more than 16 businesses at a time (Max concurrency) the entries count double.

## `complaintStatus` (type: `array`):

Only the complaints with one of these statuses (filtered by BBB). Empty = all complaints. Needs Customer complaints.

## `complaintTypes` (type: `array`):

Only the complaints of one of these types (filtered by BBB). Empty = all types. Needs Customer complaints.

## `enrichEmails` (type: `boolean`):

Visit the company's own website (home, contact and legal pages, 3 pages max) to find its email address when BBB shows none or another one. Needs **Full business profile** (the website comes from it). Charged as a separate `email-enriched` event, once per business where an email BBB does not show is found.

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

Keep only the businesses rated at least this grade (`A+` best, `F` worst). Businesses not rated by BBB (NR) are dropped when this is set.

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

Keep only the businesses accredited by BBB.

## `minYearsInBusiness` (type: `integer`):

Keep only the businesses open for at least this many years, as shown on their BBB profile. Needs Full business profile (the years are on the profile only). Businesses with no year shown are dropped when this is set.

## `distance` (type: `string`):

Search mode: keep the businesses within this many miles of **Location** (BBB's own distance filter). Empty = any distance.

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

Keep only the businesses that take quote requests through BBB ("Get a Quote").

## `requirePhone` (type: `boolean`):

Drop the businesses without a phone number.

## `requireWebsite` (type: `boolean`):

Drop the businesses without a website. Needs **Full business profile**.

## `requireEmail` (type: `boolean`):

Drop the businesses that show no email on their BBB profile (the company website is visited afterwards, only for the businesses kept: its emails do not count here). Needs **Full business profile**.

## `postedAfter` (type: `string`):

Only the reviews and complaints dated on or after this date: `2026-09-01`, or a period before now such as `7 days`, `2 weeks`, `1 month` (API: `24 hours` and full ISO date-times work too). Applies to the review and complaint lists, not to the businesses. Needs Customer reviews or Customer complaints.

## `postedBefore` (type: `string`):

Only the reviews and complaints dated on or before this date (the whole day is included), or older than a period such as `30 days`. Needs Customer reviews or Customer complaints.

## `excludeKeywords` (type: `array`):

Drop the businesses whose name or category contains one of these words (case and accents ignored).

## `onlyNew` (type: `boolean`):

Skip the businesses that a previous run (same **Memory key**) already delivered: they are not saved and not charged, and their pages are not even opened. First run = everything is new. To follow new reviews or complaints of known businesses, use **Reviews and complaints after** (e.g. `7 days`) instead.

## `stateKey` (type: `string`):

Name of the memory used by **Only new businesses**. Give each schedule / task its own key (e.g. `austin-plumbers`) so that they do not share their memory. Letters, digits, `-` and `_`.

## `resetState` (type: `boolean`):

Forget everything remembered under this **Memory key** before the run: this run returns (and charges) every business again. Untick it afterwards.

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

Apify Proxy or your own proxies. Keep the default: it is included in the price. The residential Apify proxy is not available in this Actor.

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

Maximum number of requests processed in parallel.

## `maxRequestsPerMinute` (type: `integer`):

Most requests to the site in any 60 seconds, all of them counted: search pages, profiles, review and complaint pages. A budget, not an even pace: up to this many can leave at once when the minute starts (spread them with the minimum delay below). Lower it if the site answers HTTP 429 / 403 in the log.

## `minRequestIntervalMs` (type: `integer`):

Smallest gap between two requests, in milliseconds. Unlike the per-minute rate, this spreads the requests evenly instead of letting them go out in a burst. 0 = no gap.

## `maxRequestRetries` (type: `integer`):

Retries per request before it is marked as failed. Behind a proxy, a request the site turns away is also retried on a new proxy session up to 10 times without using up these retries.

## `debugLog` (type: `boolean`):

Include debug messages in the run log.

## Actor input object example

```json
{
  "mode": "search",
  "startUrls": [],
  "query": "plumber",
  "searchQueries": [],
  "location": "Austin, TX",
  "locations": [],
  "country": "USA",
  "maxItems": 100,
  "maxItemsPerQuery": 0,
  "maximizeCoverage": true,
  "sort": "Relevance",
  "includeDetails": false,
  "includeReviews": false,
  "maxReviewsPerBusiness": 50,
  "includeComplaints": false,
  "maxComplaintsPerBusiness": 50,
  "enrichEmails": false,
  "accreditedOnly": false,
  "onlyQuoteRequests": false,
  "requirePhone": false,
  "requireWebsite": false,
  "requireEmail": false,
  "excludeKeywords": [],
  "onlyNew": false,
  "stateKey": "default",
  "resetState": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 8,
  "maxRequestsPerMinute": 120,
  "minRequestIntervalMs": 0,
  "maxRequestRetries": 5,
  "debugLog": false
}
```

# Actor output Schema

## `results` (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 = {
    "query": "plumber",
    "location": "Austin, TX",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("nice_dev/bbb-businesses-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 = {
    "query": "plumber",
    "location": "Austin, TX",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("nice_dev/bbb-businesses-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 '{
  "query": "plumber",
  "location": "Austin, TX",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call nice_dev/bbb-businesses-scraper --silent --output-dataset

```

## MCP server setup

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