# BBB Scraper — Business Leads, Ratings & Phones (`scrapersdelight/bbb-scraper`) Actor

Scrape Better Business Bureau (bbb.org) search results into clean leads: business name, phone, full street address, BBB letter rating (A+ to F), accreditation status, category and profile URL. Search any keyword across any US/Canada city or ZIP. No login, deduplicated output.

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

## Pricing

$1.80 / 1,000 per business returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## BBB Scraper — Business Leads, Ratings & Phones

Turn any **Better Business Bureau (bbb.org)** search into a clean lead list. Give it a keyword
(`plumber`, `roofing`, `dentist`, `moving company`…) and a location (`Austin, TX`, `New York, NY`,
`Toronto, ON`, or a ZIP like `78701`) and it returns one row per business with:

| Field | Example |
|---|---|
| `businessName` | `Calixto Plumbing` |
| `phone` | `(512) 945-3143` |
| `streetAddress` / `city` / `state` / `zip` / `country` | `8410 Georgian Dr # B` · `Austin` · `TX` · `78753-6003` · `US` |
| `bbbRating` | `A+` (BBB letter grade, `A+` → `F`, `null` when unrated) |
| `isAccredited` / `accreditedStatus` | `true` / `AB` |
| `bbbCategory` | `plumber`, `roofing contractors`, `commercial plumber` … |
| `bbbOfficeId` / `bbbOfficeName` | `0825` · `BBB Heart of Texas` |
| `profileUrl` | link to the BBB profile |
| `businessId` | BBB's own id — stable, and what rows are deduplicated on |
| `searchKeyword` / `searchLocation` / `searchPage` / `resultPosition` / `totalResultsForQuery` | provenance for every row |

Every keyword is run against every location, so 5 keywords × 10 cities = 50 searches in one run.

### Why the data is trustworthy

The actor reads two machine-readable blobs that BBB ships inside each search page — the JSON-LD
`SearchResultsPage` (name, phone, full postal address, profile URL) and BBB's own analytics object
(business id, letter rating, accreditation, BBB office) — and **joins them on the business id**.
Nothing is screen-scraped out of rendered HTML, and no number is grabbed by a page-wide regex.

Measured fill-rates on a 245-business run across 6 trades × 3 metros:
`businessName` 100% · `phone` 100% · `city/state/zip` 100% · `bbbRating` 99% · `bbbCategory` 100% ·
`streetAddress` 84% (BBB genuinely has no street for PO-Box-only listings).

### Filters

- `accreditedOnly` — keep only BBB Accredited businesses (BBB's search already leans heavily
  accredited; this makes it a guarantee).
- `minRating` — drop anything below a letter grade. Unrated businesses are dropped when a floor is set.
- `requirePhone` — drop rows without a phone number.
- `sort` — `Relevance` (BBB default) or `Distance`; `Distance` genuinely returns a different set.

### Known limits (measured, not guessed)

- **BBB serves at most 15 result pages per keyword + location — about 225 businesses** — even when
  it reports thousands of total results. The run log tells you when a query is capped. To cover a
  big market, split it: more keywords, more cities, or ZIP-by-ZIP.
- Consecutive result pages of the same query **overlap slightly**. Rows are deduplicated on
  `businessId`, and you are only charged for rows actually delivered — never for a duplicate.
- A location is **required**; an empty one returns nothing.
- About a quarter of BBB's responses omit the ratings blob. The actor detects this and re-requests
  the page, so ratings stay ~100% filled; on the rare page where every retry misses, `bbbRating`,
  `isAccredited` and `bbbOfficeName` come back `null` rather than wrong.
- Business **website and email are not on BBB's search pages** (they live behind the individual
  profile pages, which block automated requests). This actor is scoped to search-result leads;
  pair it with an email-enrichment actor if you need inboxes.

### Input example

```json
{
  "keywords": ["plumber", "hvac"],
  "locations": ["Austin, TX", "78701"],
  "maxPagesPerQuery": 5,
  "maxItems": 200,
  "accreditedOnly": false,
  "minRating": "",
  "requirePhone": false,
  "sort": "Relevance"
}
```

### Pricing

Pay per business returned. You are charged only for the rows delivered to your dataset — filtered
and duplicate rows cost nothing.

### Legal

Use of this actor is subject to the Better Business Bureau's Terms of Service and to the data
protection laws that apply to you. The output describes businesses, but sole-trader listings can
still constitute personal data — how you store, contact and process the results is your
responsibility. Scrape at a reasonable rate and only for lawful purposes.

# Actor input Schema

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

What to search for on BBB, e.g. "plumber", "roofing", "dentist". Every keyword is run against every location.

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

Where to search, e.g. "Austin, TX", "New York, NY", "Toronto, ON" or a ZIP like "78701". BBB requires a location — a blank one returns nothing.

## `maxPagesPerQuery` (type: `integer`):

15 businesses per page. BBB only ever serves 15 pages (~225 businesses) per query no matter how many results it reports — to go wider, add more keywords, cities or ZIPs.

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

Cost/speed guard across the whole run. 0 = no cap.

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

Keep only businesses that are BBB Accredited (they pay for accreditation — usually a higher-intent lead).

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

Drop businesses rated below this letter grade. Businesses with no rating (NR) are dropped when a floor is set. Leave blank for all.

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

Drops records with no phone number.

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

BBB's own ordering. "Distance" returns a genuinely different set (closest first) than "Relevance".

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

Apify Proxy (datacenter) is enough for bbb.org and is the default. Switch to RESIDENTIAL only if you start seeing blocked pages.

## Actor input object example

```json
{
  "keywords": [
    "plumber"
  ],
  "locations": [
    "Austin, TX"
  ],
  "maxPagesPerQuery": 5,
  "maxItems": 200,
  "accreditedOnly": false,
  "minRating": "",
  "requirePhone": false,
  "sort": "Relevance",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `records` (type: `string`):

The dataset of scraped BBB business leads (one item per business).

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "keywords": [
        "plumber"
    ],
    "locations": [
        "Austin, TX"
    ]
};

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

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

```

## MCP server setup

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