# BBB Business Directory Contacts Scraper (`automation-lab/bbb-business-directory-contacts`) Actor

Search public US BBB business directory cards by category and location. Export names, BBB ratings, categories, phones, addresses, service areas and profile URLs for company vetting.

- **URL**: https://apify.com/automation-lab/bbb-business-directory-contacts.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.30 / 1,000 business exporteds

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

## BBB Business Directory Contacts Scraper

Export public **BBB business directory contacts** for repeat company vetting and prospect-list maintenance. Search a US business category or keyword and location, then collect business names, BBB ratings, categories, phones, business addresses, service areas and source profile links.

This Actor reads search-result cards, not individual business profiles. It does not extract reviews, complaints, private emails, personal contact enrichment or hidden records. It is independently operated and is not affiliated with or endorsed by the Better Business Bureau.

### Who is it for?

- Procurement teams refreshing a supplier shortlist.
- Researchers comparing local business categories and BBB letter ratings.
- Sales operations teams maintaining public business telephone and mailing details.
- Analysts who need source-attributed directory rows rather than a website screenshot.

### Why use this Actor?

Each run returns a consistent table with the original search URL, canonical BBB profile URL and extraction timestamp. Categories and service-area summaries remain arrays rather than flattened text. Missing fields stay null or empty arrays; the Actor does not invent contact details.

The workflow deliberately avoids profile-by-profile enrichment. You receive public directory cards with a clear record limit, deduplication and observable failure behavior.

### Getting started

1. Enter a business category such as `Plumber`.
2. Enter a US city/state or ZIP code, such as `Austin, TX`.
3. Set `maxItems` and `maxPages` for your required coverage.
4. Run the Actor and inspect the Business contacts dataset view.
5. Export JSON, CSV, Excel or other dataset formats through Apify.

Example input:

```json
{"search":"Plumber","location":"Austin, TX","maxItems":30,"maxPages":5}
```

US residential proxies are selected automatically and paid by the Actor operator. No proxy subscription, login or source-site credentials are required from you.

### Inputs and matching behavior

| Input | Default | Meaning |
|---|---|---|
| `search` | Required | Category or keyword, trimmed and sent to BBB Category search. |
| `location` | Required | US city/state or ZIP, trimmed and passed to BBB. |
| `maxItems` | 30 | Global unique-profile result limit; integer 1–1000. |
| `maxPages` | 5 | Maximum search pages starting at page 1; integer 1–30. |

BBB controls matching, category expansion and result order. `search` is not a local exact-name, exact-phrase or whole-word filter. Results may include nearby businesses or businesses serving your location; an address need not be inside the exact city boundary.

Unknown fields and invalid values fail validation. There is no explicit-profile URL input, review mode, distance filter or unlimited setting. Zero is not accepted for either limit.

### Output fields

| Field | Meaning |
|---|---|
| `businessName` | Business name on the directory card. |
| `profileUrl` | Canonical BBB profile URL and deduplication key. |
| `categories` | Category names supplied by BBB. |
| `bbbRating` | BBB letter rating; not customer-review stars. |
| `phones` | Public business telephone numbers. |
| `address`, `city`, `state`, `postalCode` | Displayed business address components; street address may be withheld. |
| `hasServiceArea` | Whether BBB marks the business as serving an area. |
| `serviceAreaLabel` | Service-area description if supplied; often null. |
| `serviceAreas` | Available summary areas or ZIP codes, not a complete territory boundary. |
| `isBbbAccredited` | BBB directory accreditation flag when present. |
| `searchUrl` | Source search page for this row. |
| `scrapedAt` | UTC extraction time. |

One observed Austin directory record had these public values (timestamps and source query vary):

```json
{
  "businessName": "Calixto Plumbing",
  "profileUrl": "https://www.bbb.org/us/tx/austin/profile/plumber/calixto-plumbing-0825-1000223803",
  "categories": ["Plumber", "Plumbing Renovation", "Commercial Plumber", "Plumbing Plan"],
  "bbbRating": "A+",
  "phones": ["(512) 945-3143"],
  "address": "8410 Georgian Dr # B",
  "city": "Austin",
  "state": "TX",
  "postalCode": "78753-6003",
  "hasServiceArea": true,
  "serviceAreaLabel": null,
  "serviceAreas": ["Bell County, TX", "Burnet County, TX", "Lee County, TX", "Milam County, TX", "Travis County, TX", "Williamson County, TX"],
  "isBbbAccredited": true,
  "searchUrl": "https://www.bbb.org/search?find_country=USA&find_text=Plumber&find_loc=Austin%2C+TX&find_type=Category&page=1",
  "scrapedAt": "2026-10-21T20:30:00.000Z"
}
```

### How much does it cost to export BBB business contacts?

Pricing is pay per event: a **$0.005 start fee** once per run plus a plan-tier price for each delivered unique business. The BRONZE item price is **$0.002164 per business**; consult the Pricing tab for the applicable tier. Per-business prices are FREE $0.0024886, BRONZE $0.002164, SILVER $0.0016879, and GOLD/PLATINUM/DIAMOND $0.0012984.

At that BRONZE rate, 1 business is $0.007164, 10 businesses are $0.02664 and 100 businesses are $0.2214. These examples exclude taxes and assume the stated item count is actually returned. An empty or failed run can still incur its start fee; duplicate cards do not incur item charges.

Spend tiers follow qualifying monthly Apify Store spend, not an invented per-Actor volume schedule. Proxy and platform operating costs are included. Any financial estimates are subject to refunds, disputes, fraud, taxes, corrections and contractual clawbacks; they are not guaranteed payouts.

### Limits, coverage and failures

BBB currently returns up to 15 cards per search page. Duplicate canonical profiles can occur within and between pages. The Actor keeps the first card for each canonical profile, so multiple branch addresses under one profile are not separately exported. It may visit more pages than `ceil(maxItems/15)` to reach the requested unique count.

### Pagination and stopping rules

Collection stops at the first of: `maxItems`, `maxPages`, BBB's reported final page, an empty valid search page, a billing limit or the run deadline. A result limit is a maximum, not a promise of full coverage. Ratings and contact details can change after extraction.

Blocked pages, missing state and malformed cards are errors, not fabricated empty results. Each page gets up to three attempts with fresh US residential Chromium sessions. There is no unbounded retry or paid data-provider fallback. Later page failure leaves earlier rows as partial data and marks the run failed.

### Integrations

Export the dataset to a spreadsheet for supplier review, or send completed-run notifications to Make, Zapier or your own webhook. For recurring refreshes, schedule this Actor in Apify and compare stored snapshots downstream using `profileUrl` as a join key.

The Actor itself does not retain cross-run history, detect changes, send alerts or contact businesses. Keep full exports in your data pipeline when comparing larger lists.

### API usage

Use a managed Apify token in an environment variable; do not embed tokens in shared scripts.

```bash
curl -X POST \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  'https://api.apify.com/v2/acts/automation-lab~bbb-business-directory-contacts/runs' \
  -d '{"search":"Plumber","location":"Austin, TX","maxItems":30,"maxPages":5}'
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/bbb-business-directory-contacts').call({
  search: 'Plumber', location: 'Austin, TX', maxItems: 30, maxPages: 5
});
const { items } = await client.dataset(run.defaultDatasetId).listItems({ limit: 30 });
```

Python:

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/bbb-business-directory-contacts').call(
    run_input={'search': 'Plumber', 'location': 'Austin, TX', 'maxItems': 30, 'maxPages': 5})
items = client.dataset(run['defaultDatasetId']).list_items(limit=30).items
```

### MCP: Claude Code, Claude Desktop, Cursor and VS Code

Claude Code:

```bash
claude mcp add --transport http apify "https://mcp.apify.com?tools=automation-lab/bbb-business-directory-contacts"
```

Claude Desktop, Cursor and VS Code can use the equivalent HTTP-server configuration in their supported MCP settings (the wrapper key can differ by client):

```json
{"mcpServers":{"apify":{"url":"https://mcp.apify.com?tools=automation-lab/bbb-business-directory-contacts"}}}
```

Example prompt: “Search BBB for plumbers serving Austin, TX; collect at most 30 businesses over five pages, then show names, BBB ratings, phones and profile links.” Authenticate using your client's supported secure Apify flow.

Discover actual tool names and schemas with `tools/list`. Selecting this Actor also adds run/data helpers; it does not expose exactly one tool. Discovery is read-only, not authorization to run.

For an authorized invocation, start once and keep the returned run ID and dataset ID. They are metadata, not rows. If still running after the server wait (at most 45 seconds), check that same run with bounded 2/4/8-second backoff capped at 10 seconds and a 120-second consumer deadline. Set client timeout above the selected wait plus transport margin. On timeout, recover the known run rather than starting another; if its ID is unknown, report uncertainty. At deadline report pending and stop. Report terminal failures and any partial rows honestly.

After success, read bounded dataset pages: for example 20 rows at a time, at most 100 rows and 64 KiB serialized UTF-8 content admitted to model context. Enforce byte limits host-side before model injection; summarize or omit oversized pages with disclosure. Track source offsets, stop at exhaustion/budget and report continuation offset and truncation. If your client cannot intercept oversized responses, do not claim a hard byte guarantee. Full exports belong in storage, not model context. Client discovery/caching varies; no numeric token savings are promised.

### Legality, responsible use and privacy

Use public business data only for a lawful, appropriate purpose and comply with source terms, privacy rules and marketing laws. Public availability is not consent for unsolicited contact. The Actor does not make legal or procurement decisions for you.

Inputs are sent to BBB through Apify's US residential proxy network. Business names, public business phones, addresses and source links are written to your run's default dataset. Some businesses are sole traders, so public business details can still be personal data. The Actor does not extract private email addresses, reviews, complaints or other profile enrichment.

The runtime uses no AI model. The generated illustration is a development asset, not an inference service processing your inputs or scraped records. Browser sessions are ephemeral and closed after each attempt; there is no cross-run contact cache. Dataset and log retention follows your Apify account's storage settings. Delete datasets, key-value stores and runs through Apify when no longer needed; the Actor does not independently erase your saved exports.

### Diagnostics and retention

Failed operations send sanitized diagnostic input, exceptions and Actor/build/run IDs to our private GlitchTip service for repair. Secret fields and URL queries are removed and reports are retained for 30 days. Normal logs contain counters and safe status summaries, not contact rows or proxy credentials.

### FAQ and troubleshooting

**Why are fewer businesses returned than requested?** BBB may have fewer results, duplicate profiles, a source page cap or a run/billing limit. Increase `maxPages` within its supported range only when more source pages exist.

**Why is an address or service-area label null?** BBB does not supply every field for every card. Null does not mean the business lacks that information elsewhere.

**Why does a plumber query include another primary category?** BBB category expansion can return a business with multiple categories. Inspect the `categories` array and source page; this is not exact primary-category filtering.

**What if the run fails with a directory HTTP or search-state error?** The source may be challenging the browser or changing its format. The Actor already retries with fresh sessions. Do not interpret an earlier partial dataset as a complete search. Report the run ID to support.

**Can I get reviews, complaint histories or private contacts?** No. This product exports public directory cards only.

### Related workflows and support

For other public business-directory workflows, browse the [automation-lab portfolio](https://apify.com/automation-lab). Coverage and fields vary by Actor; this tool does not combine other directories automatically.

Open an issue on this Actor's Apify page and include the run ID, expected behavior and a safe input example. Do not include credentials or private customer data. We aim to provide a substantive response to user-reported Store issues within 14 calendar days.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/bbb-business-directory-contacts/changelog.md

# Actor input Schema

## `search` (type: `string`):

Trimmed text sent to BBB's Category search. BBB determines category expansion and ranking; this is not a local exact-name or whole-word filter.

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

Trimmed US city/state or ZIP code passed to BBB, for example Austin, TX. Results may include nearby businesses and businesses serving that area; not an exact city boundary.

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

Global maximum unique canonical BBB profiles saved in this run, after deduplication. Default 30; 1–1000. Zero/unlimited is unsupported. Collection also stops at maxPages, BBB exhaustion, run timeout or billing limit.

## `maxPages` (type: `integer`):

Maximum BBB pages visited from page 1 for this search. Default 5; 1–30. BBB currently returns up to 15 cards per page. Stops earlier at maxItems or BBB's reported last page. Retries do not count as new pages.

## Actor input object example

```json
{
  "search": "Plumber",
  "location": "Austin, TX",
  "maxItems": 10,
  "maxPages": 5
}
```

# Actor output Schema

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

Collected BBB business directory cards.

# API

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

## JavaScript example

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

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

// Prepare Actor input
const input = {
    "search": "Plumber",
    "location": "Austin, TX",
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/bbb-business-directory-contacts").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "search": "Plumber",
    "location": "Austin, TX",
    "maxItems": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/bbb-business-directory-contacts").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "search": "Plumber",
  "location": "Austin, TX",
  "maxItems": 10
}' |
apify call automation-lab/bbb-business-directory-contacts --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/bbb-business-directory-contacts"
        }
    }
}
```

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/wigBIOTEdQ9DPDArK/builds/yTZ6yOJMp47lymGxI/openapi.json
