# Chamber of Commerce Member Directory Scraper — 927 Chambers (`scrapersdelight/chamber-directory-scraper`) Actor

Sweep many local Chamber of Commerce member directories at once (ChamberMaster). 927 chambers verified live, holding 482,110 businesses: name, category, phone, website, domain, full address. Filter by state, city or category. A free discovery run lists every bundled chamber. No login.

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

## Pricing

$4.00 / 1,000 per chamber member 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/actors/running/actors-in-store.md#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

## 🏛️ Chamber of Commerce Member Directory Scraper — 927 Chambers

> **Turn any local [Chamber of Commerce](https://www.chambermaster.com) member directory — or 927 of them at once — into a spreadsheet of real local businesses. One row per chamber member, carrying `name`, `category`, `categories[]`, `phone`, `website`, `domain`, `street`, `address2`, `city`, `state`, `zip`, `address`, `fax`, `membershipLevel`, `description`, `chamber` and `detailUrl`. Filter by state/province, city, or category keyword (insurance, roofing, dental, restaurants…), or paste your own chamber's host or directory URL. No login, no API key, no CAPTCHA, no proxy required.**

**927 chamber hosts holding 482,110 member businesses ship inside the Actor**, every one of them fetched and counted live on 2026-08-12 — not quoted from a marketing page. **$0.004 per member returned ($4 per 1,000)**, and a run that only lists the bundled chambers is **free**.

Chamber members are the cleanest local-SMB list there is: they are established, they pay dues every year, and they publish their own contact details on purpose. This Actor sweeps them at metro scale.

**Click *Try for free* and press *Start*.** The Actor ships prefilled with two real chambers (Kansas City and Memphis) — the prefilled input returned **100 rows in 8 seconds** on Apify on 2026-08-13, with zero edits.

***

### 📊 How big is the corpus? (measured, not estimated)

Every number below was measured live on **2026-08-12** by fetching every host, not by guessing:

| | |
|---|---|
| Hosts checked | **1,034** (harvested from the Common Crawl URL index for `chambermaster.com`) |
| **Live with a populated directory** | **927** |
| **Member businesses across them** | **482,110** |
| Median directory | **438** members |
| Largest | **6,108** members (North Orange County Chamber) |
| Not usable | 107 — 58 returned HTTP 404, 48 render an empty/migrated directory, 1 would not connect |
| **Independent re-check, same day** | **150 of the 927** hosts re-fetched (60 spread evenly across the size ranking + 90 drawn at random): **150/150** answered HTTP 200 with a populated directory, and their live member counts summed to **78,869** against **78,871** in the shipped list — a **0.003%** difference, 137 of 150 matching exactly |
| **Whole-corpus re-fetch, same day** | every one of the **927** hosts fetched a second time to read each chamber's own display name: **927/927** answered HTTP 200 on a direct connection and **927/927** yielded a name |

Almost all of the 927 are chambers of commerce: **888 of the 927 (95.8%)** carry "Chamber" in their own published name. The rest are boards of trade, business associations and similar member bodies running their directory on the same ChamberMaster platform — same endpoints, same fields, same output shape.

The verified host list — **name, member count and directory URL for all 927** — ships *inside* the Actor, so you never have to hunt for URLs. Pick `metro50`, `metro200` or `all`, choose from the 200-chamber dropdown by name, or paste your own.

***

### 🚀 Quick start

1. Click **Try for free** → the input opens **already filled in**.
2. Press **Start**. Two chambers (Kansas City and Memphis), 50 members each, 200-row cap. Measured on Apify 2026-08-13: **100 rows in 8 seconds**, all 100 billed as delivered.
3. Open the **Dataset** tab → **Export** to CSV, Excel, JSON, XML or pull it from the API.
4. Now make it yours: clear **Specific chambers**, pick your own from the **Chamber picker** dropdown, or type your chamber's host.

**Not sure your chamber is one of the 927?** Tick **Discovery mode** and press Start. That run returns one row per bundled chamber — rank, chamber name, host, member count, directory URL — and **is not charged**, because those rows are pushed without the billing event. Measured on Apify 2026-08-13: **927 rows in 4.7 seconds, charged events: 0**. Then copy the `host` value into **Specific chambers** and run for real.

#### The input it ships with

```json
{
  "chamberHosts": ["kcchamber", "memphischamber"],
  "hostPreset": "sample",
  "enrichDetails": true,
  "maxMembersPerHost": 50,
  "maxItems": 100
}
```

Sending an **empty input** (`{}`) runs exactly that sample — two real chambers, 100 rows, about ten
seconds — instead of starting a large sweep you did not ask for. Point it somewhere else with
`chamberHosts`, the **Chamber picker**, or `hostPreset`.

#### Free chamber discovery

```json
{ "listChambers": true }
```

#### Call it from your own code

```python
## pip install apify-client
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("scrapersdelight/chamber-directory-scraper").call(run_input={
    "chamberHosts": ["kcchamber"],
    "enrichDetails": True,
    "maxItems": 1000,
})
for member in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(member["name"], member["category"], member["phone"], member["website"])
```

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('scrapersdelight/chamber-directory-scraper').call({
    hostPreset: 'metro200',
    categoryContains: ['insurance'],
    withPhoneOnly: true,
    maxItems: 5000,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, 'members');
```

```bash
## Start a run and wait for the rows in one call
curl -X POST "https://api.apify.com/v2/acts/scrapersdelight~chamber-directory-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H 'Content-Type: application/json' \
  -d '{"chamberHosts":["memphischamber"],"states":["TN"],"maxItems":100}'
```

***

### 🎯 The wedge: category and website, on 927 chambers, with a free coverage check

Three things separate this from any other way of getting chamber members:

**1. The business category, which the directory page does not contain.** A chamber's listing page carries the name, phone and postal address — but the *business category* is only ever on the member's own detail page, and the listing page shows a website for only **8.5% of members** (41,219 links across all 482,110 rows; only 378 of the 927 hosts render one at all). This Actor opens each member's detail page by default, which takes **category from 0.0% to 96.0% fill and website from 12.8% to 86.3%**, measured on a 1,000-row run across 40 chambers. Category is what turns "a list of businesses" into "every insurance agency in this metro". The price per member is identical either way — you are trading run time for fields, never money.

**2. Scale that is measured, not promised.** 927 chambers, 482,110 members, every host fetched and counted on 2026-08-12, with a same-day 150-host re-check agreeing to within 0.003%. You do not supply URLs and you do not find out mid-run that your region is not covered.

**3. A free way to check coverage before you spend anything.** `listChambers: true` returns all 927 chambers with their names, hosts and member counts, and charges nothing. Most directory scrapers make you buy first and discover coverage second.

***

### ⚠️ Read this before you buy rows

1. **A chamber's members are not all in one town.** Big-city chambers carry out-of-state corporate members — Kansas City's list includes a Southwest Airlines address in Dallas. If you need tight geography, set `states` and/or `cities`; they filter on what the chamber published, and state fill is 98.4%.
2. **Two record types can appear in one dataset.** Discovery mode (`listChambers: true`) writes *chamber* rows (`recordType: "chamber"`, with `rank`, `members`, `directoryUrl`), and a normal run writes *member* rows (`recordType: "member"`). Filter on `recordType` if you ever run both into the same dataset.
3. **`maxItems` caps rows, not the search.** Filters are applied across the **whole** directory of every selected chamber before the cap bites. Asking for 40 insurance members from Memphis still walks all **1,541** members it published on 2026-08-13 to find the ones that match. That costs run time, never money — you are billed per row *returned*.
4. **The bundled member counts are a 2026-08-12 snapshot.** Chambers add and drop members daily. Memphis read **1,537** in the corpus sweep and **1,541** when re-walked the next day. The counts tell you how big a directory is; the run itself always reads the live page.
5. **`enrichDetails: false` empties the category column.** It roughly halves run time and costs exactly the same per row — but it ships `category` at 0.0% and `website` at 12.8%. Turn it off only when you want names, phones and addresses fast.
6. **Chambers differ in speed by 100x, and the run respects your timeout.** Measured 2026-08-17: `kcchamber` answers a page in ~0.2 s, while `fullertonchamber` — the largest bundled directory — answers *every* request on a fixed ~21 s server-side delay. Rows are pushed to the dataset as each batch completes, and the run watches its own clock: if a chamber cannot finish another batch in the time left it moves on to the next chamber, and if the whole budget is spent the run ends **successfully** with everything it collected (and a warning saying so). You are only ever charged for rows actually returned, so a short timeout costs you rows, never money. Give a big sweep a generous run timeout. It is also why the default preset is the fast 2-chamber `sample`: an empty input never starts a 50-chamber sweep you did not ask for.

***

### 🔍 What you get

One row per chamber member. All the fields, grouped:

**Identity**

| Field | Type | Notes |
|---|---|---|
| `recordType` | string | `"member"` on every scraped member row |
| `name` | string | The member business, 100% fill |
| `memberId` | string | The chamber's own `data-memid` — the dedupe key, unique within a chamber |
| `chamber` | string | The chamber's own published name, e.g. *Greater Kansas City Chamber of Commerce* |
| `chamberHost` | string | e.g. `kcchamber.chambermaster.com` |

**What the business does**

| Field | Type | Notes |
|---|---|---|
| `category` | string | The first category the chamber files them under, e.g. *Roofing Contractors* |
| `categories` | string\[] | Every category, e.g. `["Accounting, Bookkeeping and Tax Services", "Tax Preparation and Consulting"]` |
| `description` | string | The member's own blurb, as published (long ones are truncated by the chamber itself with a trailing "… read more") |
| `membershipLevel` | string | The member's dues tier where the chamber shows it — *Basic*, *Gold*, *Community* etc. `null` where the chamber does not publish tiers |

**How to reach them**

| Field | Type | Notes |
|---|---|---|
| `phone` | string | Normalised to `(816) 363-4600` for 10-digit NANP numbers; anything else is kept verbatim |
| `fax` | string | Only where the chamber publishes one |
| `website` | string | The member's own site, absolute URL |
| `domain` | string | The bare hostname (`www.` stripped) — the join key for enrichment |

**Where they are**

| Field | Type | Notes |
|---|---|---|
| `street`, `address2` | string | Street line and suite/unit line |
| `city`, `state`, `zip` | string | As the chamber publishes them; `state` covers US states and Canadian provinces, `zip` covers ZIP and Canadian postal codes |
| `address` | string | The above joined into one line, for a single-column paste |

**Provenance**

| Field | Type | Notes |
|---|---|---|
| `detailUrl` | string | The member's page on the chamber site, 100% fill |
| `sourceUrl` | string | The directory endpoint the row came from |
| `scrapedAt` | string | ISO-8601 UTC timestamp of the run |

Discovery mode rows carry a different, smaller shape: `recordType: "chamber"`, `rank`, `chamber`, `chamberHost`, `host`, `members`, `directoryUrl`, `measuredAt`.

***

### 📏 Measured field fill

#### A. 1,000 rows across 40 chambers — the same input run twice, with and without detail pages

`hostPreset: metro50`, 25 members per chamber, 1,000 rows, no filters:

| Field | Fill (detail pages **on**, the default) | Fill (detail pages **off**) |
|---|---|---|
| Business name | **100.0%** | 100.0% |
| Chamber | **100.0%** | 100.0% |
| City / State | **98.1% / 98.4%** | 98.0% / 98.1% |
| ZIP | **96.1%** | 95.5% |
| Street | **95.2%** | 94.6% |
| Phone | **96.7%** | 96.1% |
| **Category** | **96.0%** | **0.0%** |
| **Website** | **86.3%** | **12.8%** |
| Membership tier | 77.5% | 77.5% |
| Description | 58.5% | 26.1% |
| Fax | 7.1% | 0.0% |

**Category and Website are the whole reason `enrichDetails` defaults to on.** Everything else is within ±1% either way.

#### B. A second, independent 200-row sample — 8 chambers, 25 each, measured 2026-08-13

Sorted by fill so the sparse fields are impossible to miss:

| Field | Fill |
|---|---|
| `name`, `detailUrl`, `recordType` | **100.0%** |
| **`category`** | **97.0%** |
| `state` | 97.0% |
| `city` | 95.0% |
| **`phone`** | **94.0%** |
| **`website`** / `domain` | **89.0%** |
| `street` | 89.0% |
| `zip` | 88.5% |
| `membershipLevel` | 87.5% |
| `description` | 73.0% |
| `fax` | **4.0%** |
| `address2` | **1.0%** |

**The number that could mislead you: `fax` and `address2` are almost empty *in this sample* (4.0% and 1.0%).** They exist because some chambers publish them and dropping the column would lose real data.

**And the honest counterweight: those two sparse fields are chamber-dependent, not globally sparse.** Re-measured on the two chambers the Actor ships prefilled (Kansas City + Memphis, 100 rows): **`fax` came back at 52% and 70%** — an order of magnitude above the 4.0% in the table above. The same swing runs the other way on `description`, which is 73.0% here and 58.5% in sample A but only **26–28%** on those same two prefill chambers. `membershipLevel` swings hardest of all: 87.5% here, 77.5% in sample A, **0%** on both prefill chambers. The lesson is not "trust this table" — it is that per-chamber publishing habits dominate every optional field, which is why every run prints its own fill in the log. Do not size a workflow on a single sample; run your own chambers and read the log line.

**Every run prints its own measured fill in the log**, so you never have to trust this table for the slice you actually pulled:

```
Field fill — phone 94.0% · website 89.0% · category 97.0% · street 89.0% · city 95.0% · state 97.0% · zip 88.5%
```

***

### 📄 Sample rows (real, from a live run)

A US member with a fax and no published dues tier:

```jsonc
{
  "recordType": "member",
  "chamberHost": "manateechamber.chambermaster.com",
  "chamber": "Manatee Chamber of Commerce",
  "memberId": "1335",

  "name": "Cablish & Gentile, CPAs, LLC",
  "category": "Accounting, Bookkeeping and Tax Services",
  "categories": [
    "Accounting, Bookkeeping and Tax Services",
    "Tax Preparation and Consulting"
  ],
  "description": "Cablish & Gentile, CPAs, LLC has been providing personalized service to the local and national community for more than 40 years. Our desire is to make you and your business successful.",
  "membershipLevel": null,          // this chamber does not publish dues tiers

  "phone": "(941) 756-9527",
  "fax": "(941) 756-9529",
  "website": "http://www.cgg-cpa.com",
  "domain": "cgg-cpa.com",

  "street": "4855 27th Street West",
  "address2": null,
  "city": "Bradenton",
  "state": "FL",
  "zip": "34207",
  "address": "4855 27th Street West, Bradenton, FL, 34207",

  "detailUrl": "https://manateechamber.chambermaster.com/list/member/cablish-gentile-cpas-llc-1335",
  "sourceUrl": "https://manateechamber.chambermaster.com/list/search?q=&c=&sa=true",
  "scrapedAt": "2026-08-13T02:16:41.131Z"
}
```

A Canadian member on a chamber that *does* publish dues tiers:

```jsonc
{
  "recordType": "member",
  "chamberHost": "businessinsurrey.chambermaster.com",
  "chamber": "Surrey & White Rock Board of Trade",
  "memberId": "47620",

  "name": "Altius Health & Performance",
  "category": "Health & Wellness",
  "categories": ["Health & Wellness", "Health Resorts & Spas", "Rehabilitation Services"],
  "membershipLevel": "Gold",

  "phone": "(604) 541-9336",
  "website": "http://www.altiushp.ca",
  "domain": "altiushp.ca",

  "street": "1938 152 St.",
  "city": "Surrey",
  "state": "BC",
  "zip": "V4A4N6",          // Canadian postal code, same field as a US ZIP
  "address": "1938 152 St., Surrey, BC, V4A4N6",

  "detailUrl": "https://businessinsurrey.chambermaster.com/list/member/altius-health-performance-47620",
  "scrapedAt": "2026-08-13T02:14:34.718Z"
}
```

And a discovery-mode row (free):

```jsonc
{
  "recordType": "chamber",
  "rank": 1,
  "chamber": "North Orange County Chamber",
  "chamberHost": "fullertonchamber.chambermaster.com",
  "host": "fullertonchamber",
  "members": 6108,
  "directoryUrl": "https://fullertonchamber.chambermaster.com/list",
  "measuredAt": "2026-08-12"
}
```

Three things people misread:

- **`membershipLevel: null` means the chamber publishes no tier**, not that the member is unranked. Whole chambers are either all-tiered or all-null.
- **`state` and `zip` are bilingual columns.** `BC` / `V4A4N6` sit in the same fields as `FL` / `34207`. If you feed them into a US-only address validator, filter to US states first.
- **`category` is the chamber's taxonomy, not yours.** Different chambers file a roofer under *Roofing Contractors*, *Contractors — Roofing*, or *Home Improvement*. That is why `categoryContains` does a case-insensitive substring match across `category`, `categories[]`, `name` and `description` rather than an exact match.

***

### 🧭 How to run it — named recipes

#### Recipe 1: "Is my chamber covered?" (free)

```json
{ "listChambers": true }
```

927 rows, 4.7 seconds on Apify, **no charge** (the run reports `member-scraped: 0`). Sort by `members`, find your chamber, copy its `host`.

#### Recipe 2: One chamber, whole directory

```json
{ "chamberHosts": ["kcchamber"], "hostPreset": "none", "maxItems": 2000 }
```

Paste the short host (`kcchamber`), the full host (`kcchamber.chambermaster.com`) or the URL you are looking at (`https://kcchamber.chambermaster.com/list`) — all three are accepted.

#### Recipe 3: Pick chambers by name, no subdomain guessing

```json
{ "chamberPicker": ["memphischamber", "manateechamber"], "maxItems": 3000 }
```

The dropdown lists the 200 largest chambers as *"Greater Memphis Chamber — 1,537 members (memphischamber)"*, so you never have to know the host.

#### Recipe 4: Every insurance-adjacent business across the 200 biggest chambers

```json
{
  "hostPreset": "metro200",
  "categoryContains": ["insurance", "risk management"],
  "withPhoneOnly": true,
  "maxItems": 5000
}
```

#### Recipe 5: A whole state

```json
{ "hostPreset": "all", "states": ["TX"], "withWebsiteOnly": true, "maxItems": 10000 }
```

#### Recipe 6: The no-website pitch list (for web and marketing agencies)

```json
{ "hostPreset": "metro200", "enrichDetails": true, "withWebsiteOnly": false, "maxItems": 5000 }
```

Then filter the export to `website == null`. On the measured samples that is **11.0–13.7%** of members — established, dues-paying businesses with no website at all.

#### Recipe 7: A wide, shallow regional sample

```json
{ "hostPreset": "metro200", "maxMembersPerHost": 25, "maxItems": 5000 }
```

25 members each from 200 chambers instead of exhausting the first one.

#### Recipe 8: Fast and cheap — names, phones and addresses only

```json
{ "hostPreset": "metro50", "enrichDetails": false, "maxItems": 2000 }
```

Roughly half the run time, **identical price per row**, and an empty `category` column (`website` drops to 12.8%).

#### Recipe 9: Monitor a chamber for new members

Attach an Apify **Schedule** (daily or weekly) to Recipe 2 and diff `memberId` between runs. For one association with built-in Slack / webhook / email alerts on new joiners, use our **[GrowthZone Directory Scraper](https://apify.com/scrapersdelight/growthzone-directory-scraper)** instead — that is the job it was built for.

***

### ⚙️ Input

Fields in schema order. Everything has a default or a prefill, so a zero-edit run works.

| Field | Type | Default | What it does |
|---|---|---|---|
| `listChambers` | boolean | `false` | **Free discovery mode.** Skips scraping and returns all 927 bundled chambers (rank, name, host, member count, directory URL). Pushed without the billing event, so it costs $0 in Actor charges. |
| `hostPreset` | select | `sample` | Which slice to sweep when you name no chambers: `sample` (the 2 sample chambers — fast, the default) · `metro50` · `metro200` · `all` (927 chambers / 482,110 members) · `none`. The bigger presets are ordered largest-first, and the largest chambers answer on a ~21 s server-side delay per page — give `metro50` / `metro200` / `all` a run timeout of an hour or more. |
| `chamberPicker` | multi-select | `[]` | The 200 largest chambers **by name**, each labelled with its measured member count. Overrides the preset. |
| `chamberHosts` | string list | prefilled `["kcchamber","memphischamber"]` | Any ChamberMaster chamber, bundled or not. Accepts `kcchamber`, `kcchamber.chambermaster.com` or a full directory URL. Overrides the preset. **Clear it** to use the preset. |
| `enrichDetails` | boolean | `true` | Open each member's detail page. Category 0.0% → 96.0%, website 12.8% → 86.3%. Roughly doubles run time. **Same price either way.** |
| `categoryContains` | string list | `[]` | Case-insensitive substring match across category, categories\[], name and description. Needs `enrichDetails` on to match real categories. |
| `states` | multi-select | `[]` | A 65-entry dropdown: 50 US states + DC + Puerto Rico, and 13 Canadian provinces and territories. No more typos killing a run, and rows that publish a full name ("Illinois", "British Columbia") are mapped back to the code before filtering. |
| `cities` | string list | `[]` | Exact city names as the chamber publishes them (`Kansas City`, not `KC`). Case-insensitive. |
| `withPhoneOnly` | boolean | `false` | Drops rows with no phone — about 1 in 30 at 96.7% fill. |
| `withWebsiteOnly` | boolean | `false` | Drops rows with no website (86.3% fill with detail pages on, 12.8% without). |
| `maxItems` | number | `100` | **Your hard cost cap.** Rows returned *and* charged. 100 = $0.40, 200 = $0.80, 1,000 = $4, the whole corpus = ~$1,928. Filters run over the whole directory first, so this never cuts a search short. |
| `maxMembersPerHost` | number | `0` (prefill `50`) | Spread the budget across chambers instead of exhausting the first. `0` = no per-chamber limit. |
| `detailConcurrency` | number | `8` | Parallel detail-page requests, 1–16. Every measurement on this page was taken at 8. Does not change price. |
| `proxyConfiguration` | proxy | off | Not needed — see the transport ladder below. Set one only if you need a fixed egress IP. |

***

### 💵 Pricing

**$0.004 per chamber member returned — $4 per 1,000.** Pay-per-event, charged on the `member-scraped` event. No monthly platform fee from this Actor.

| Run | Members | Cost |
|---|---|---|
| The prefilled first run (2 chambers × 50, 200-row cap) | 100 | **$0.40** |
| One mid-size chamber, whole directory | 1,000 | **$4.00** |
| Greater Kansas City Chamber, complete | 1,858 | **$7.43** |
| A metro200 category sweep | 5,000 | **$20.00** |
| **All 927 chambers, end to end** | **482,110** | **~$1,928** |

Apify's free tier includes $5 of monthly usage credit, which is about **1,250 members** before you pay anything.

**What is *not* billed:**

- **Discovery mode is free.** `listChambers: true` pushes its 927 rows without the charge event. Checking coverage costs nothing.
- **Rows removed by your filters are never charged.** A `categoryContains` sweep may walk 200,000 members to return 3,000 — you pay for the 3,000. The search costs the Actor run time and its own bandwidth, not your money.
- **Duplicates are never charged twice.** Members are deduplicated on the chamber's own `memberId` *before* anything is fetched or pushed (measured: 0 duplicates in 14,083 contiguous cards — see below).
- **Detail-page fetches are not billed.** `enrichDetails` costs run time only; the price per member is identical on and off.
- **Rows are charged as they are pushed** (`Actor.pushData(items, 'member-scraped')`), so if you hit a budget cap you get whole rows and stop — never a half-billed dataset.

`maxItems` is your cost dial and it is a hard cap. Set it to what you are willing to spend × 250.

***

### 🚧 Honest limits

- **No email addresses.** ChamberMaster puts member email behind a JavaScript "Send Email" contact form — there is no `mailto:` anywhere on these directories. Nobody can scrape an email off the chamber page, us included, and this Actor does not pretend to. **What to do instead:** every row carries `domain` (89.0% fill), which is exactly the input our **[Decision-Maker Email Finder](https://apify.com/scrapersdelight/decision-maker-email-finder)** and **[Local Business Contact Enricher](https://apify.com/scrapersdelight/local-business-enricher)** take. Chamber list → domains → decision-maker emails is a two-Actor pipeline.
- **No contact-person names.** Some detail pages list a staff rep. We deliberately do not collect people data — this Actor returns **business listings only**.
- **No "member since" / join date.** 74 chambers were checked at random across both page templates: **zero** publish it. Rather than ship a column that is always empty, there isn't one.
- **Every optional field is chamber-dependent, so a single sample under- or over-sells it.** Measured spreads: `fax` 4.0% on the 200-row / 8-chamber sample but **52% and 70%** on the two chambers in the shipped prefill; `address2` 1.0–2.5%; `description` 58.5–73.0% on the wide samples but **26–28%** on those same prefill chambers; `membershipLevel` 87.5% / 77.5% / **0%**. A chamber either publishes a given field for everyone or for nobody. Read the fill line your own run prints rather than planning against these numbers.
- **`description` is truncated by the chamber, not by us.** Long blurbs arrive ending in "… read more" because that is literally what the page renders.
- **A chamber's members are not all in one town.** Big-city chambers carry out-of-state corporate members. Use `states` / `cities` for tight geography, and accept that a member with no published state (1.6–4.0% of rows across the measured samples) is dropped by that filter.
- **`state` is whatever the member typed, not a validated code.** Measured on the 200-row sample: ~3% of rows carry a full name ("Illinois", "Texas"), a dotted province ("B.C."), a country ("United States") or a stray postal code instead of a 2-letter code. The Actor maps every full state and province name in the dropdown back to its code before filtering, so those rows are kept — but a row whose state field holds a *country* or a postal code cannot be matched by any state filter and will be dropped by it.
- **The bundled 927 is a harvest, not a registry.** The host list came from the Common Crawl URL index, so a live ChamberMaster chamber that Common Crawl never indexed is missing from it — the Greater Kansas City Chamber, which this Actor ships *prefilled* and which returns 1,858 members, is itself not one of the 927 and will not show up in discovery mode. Paste any host you like; the bundled list is a convenience, not a boundary.
- **The same business can legitimately appear under two chambers.** Measured on 8 complete directories: 145 of 13,797 distinct business names (**1.05%**) appear in more than one, and they are overwhelmingly national brands with genuinely separate local branches — Costco Wholesale in 5 of the 8, Renewal by Andersen in 5, Salvation Army, Better Business Bureau, Express Employment Professionals, T-Mobile, Bank of America in 3–4 each. Rows are unique per `chamberHost` + `memberId`, which is the correct behaviour: those *are* different locations. Dedupe on `domain` if you want one row per brand.
- **Coverage is what ChamberMaster hosts, not every chamber in America.** Chambers that run their directory on a different platform, or on their own CMS, are not here. That is a platform boundary, not a scraping failure — and it is why discovery mode is free.
- **The 482,110 figure is a 2026-08-12 census, not a live counter.** Directories drift a few members a day (Memphis: 1,537 → 1,541 in 24 hours). Runs always read the live page.

***

### 🔧 How it works

- **One request per chamber.** `GET /list/search?q=&c=&sa=true` returns the *entire* directory in a single server-rendered response — no JavaScript, no login, no API key, no pagination to get wrong. Kansas City's 1,858 members arrive in one 3.2 MB page; the largest response measured was 18.6 MB.
- **Completeness is cross-checked, not assumed.** The single sweep was verified against an independent axis: walking `/list/searchalpha/a` … `/z` on the Greater Charlottetown Area Chamber returned **962 unique members**, and the one-shot sweep returned **962** — zero missing, zero duplicates. If a chamber ever reports more results than it renders, the Actor automatically falls back to that a–z walk and unions the pages, asserting IDs stay unique as they merge.
- **Both ChamberMaster skins are parsed.** The platform ships a modern card layout (`div.gz-results-card`, 751 of the 927 hosts) and a legacy listing layout (`div.mn-listing`, the other 176). Both are read into **one output shape** with nullable fields — never two schemas in one dataset.
- **Detail pages are fetched in parallel and merged onto the listing row.** A listing value always wins; the detail page only fills gaps. Categories exist nowhere else.
- **Filters are pushed as early as they can be trusted.** A row the *listing page* already disqualifies on state or city is dropped before its detail page is ever fetched, so a narrow geographic filter costs the site fewer requests. A row with a *null* state survives to be judged after enrichment, so filtering never silently loses a row the detail page would have saved.

#### Transport ladder — measured, and the reason the proxy default is "off"

All of it measured against the live site, no home-broadband result presented as a shippable rung:

| Rung | Result |
|---|---|
| Direct, no proxy — first full sweep | **1,033 / 1,034** hosts answered HTTP 200 (one refused to connect at all) |
| Direct, no proxy — same-day 150-host re-check | **150 / 150** answered HTTP 200 with a populated directory |
| Direct, no proxy — same-day whole-corpus re-fetch | **927 / 927** hosts answered HTTP 200, 927/927 yielded the chamber's name |
| Apify **datacenter** proxy | **1,536 / 1,537** requests succeeded — **99.9%** |
| Apify **residential** proxy | Not needed. There is nothing to defeat. |

ChamberMaster serves plain server-rendered HTML with **no anti-bot challenge, no CAPTCHA and no rate limiting observed** across the whole 927-host sweep. That is why `proxyConfiguration` is **off** by default: proxying a clean host only adds a way to fail, and it saves you the proxy cost. The field is left available if you need a fixed egress IP for your own reasons.

#### Uniqueness and dedupe — measured on contiguous full-directory walks

Not sampled offsets: eight complete directories, every card on the page.

| Chamber | Cards rendered | Unique `memberId` | Duplicates |
|---|---|---|---|
| Greater Kansas City | 1,858 | 1,858 | 0 |
| Greater Memphis | 1,541 | 1,541 | 0 |
| Greater Charlottetown Area | 962 | 962 | 0 |
| Manatee | 2,021 | 2,021 | 0 |
| Oak Park–River Forest | 2,156 | 2,156 | 0 |
| Lubbock | 1,946 | 1,946 | 0 |
| Halifax | 1,999 | 1,999 | 0 |
| Bryan–College Station | 1,600 | 1,600 | 0 |
| **Total** | **14,083** | **14,083** | **0 (0.000%)** |

Deduplication happens on the chamber's own `memberId` **in a map built as the page is parsed** — before any detail page is fetched and before anything is pushed. The dataset, and your bill, can never hold the same member of the same chamber twice. Across chambers, 1.05% of distinct business names recur, for the honest reason described in *Honest limits*.

Across runs, there is no automatic memory: a second run of the same input returns the same members again. Use different `chamberHosts`, or diff on `chamberHost` + `memberId` after export.

***

### 🛑 When a run fails

The Actor is built to fail loudly rather than hand you a green run with an empty dataset.

- **Zero rows throws.** The error names how many hosts were queued, how many were unreachable or empty, and points at the likely cause (over-narrow filters, a mistyped host, or a changed endpoint).
- **A bad host is reported, not silently skipped into nothing.** A host that 404s or refuses is logged by name with the reason; the run continues on the rest, and the final count tells you how many hosts were dead.
- **A chamber that renders zero members is called out** — "chamber may have migrated or emptied its list" — because that is a real state for 48 of the 1,034 candidates we tested.
- **An under-reported directory triggers the a–z fallback** rather than shipping a partial roster, and the merged ID count is logged.
- **Every run prints its own field fill**, so a degraded parse is visible in the log rather than discovered in your CRM.
- There is **no legitimate zero-row success**. If you get rows, they are real; if you get none, the run is red.

***

### 👤 Who buys this

#### Merchant services and payments reps

Chamber members are card-accepting local businesses with a published phone at 94–96.7% fill. `category` tells you which ones are restaurants and retail — the highest-volume merchant categories — before you dial.

#### Commercial insurance brokers and agencies

Set `categoryContains` to `["contractor", "roofing", "trucking", "restaurant"]` across `metro200` and you have a book to quote, with the address you need for a rating territory. Chamber membership itself is a proxy for a business established enough to carry real premium.

#### Payroll, PEO and benefits brokers

Chamber members cluster in the 5–200 employee band that PEOs sell into. `membershipLevel` (where published) separates the Gold-tier businesses paying serious dues from the basic-tier sole traders.

#### MSPs and IT providers

Filter to professional services — `["accountant", "law", "insurance", "medical", "engineering"]` — in one metro. `domain` at 89.0% fill is your input for a tech-stack lookup before the first call.

#### Web and marketing agencies

The **11.0–13.7% of chamber members with no website at all** are the entire pitch: an established, dues-paying business that is invisible online. Run with `withWebsiteOnly: false` and filter the export to `website == null`.

#### Lead-gen agencies and data resellers

927 chambers × a filterable category and geography axis is a repeatable product: one geo-targeted SMB list per client, priced at $4 per 1,000 rows in, and `domain` as the join key into your enrichment stack.

#### Economic-development and market research

`categories[]` across a whole chamber is a census of a local economy by sector. Two runs six months apart show you what a metro gained and lost.

***

### 🔌 Integrations

The dataset is a normal Apify dataset, so everything that speaks Apify speaks to this Actor.

- **Google Sheets / Excel** — Dataset tab → Export → CSV/XLSX, or point Sheets at the dataset's CSV URL for a self-refreshing tab.
- **Make, Zapier, n8n** — use the official Apify app/node: *Run Actor* → *Get dataset items*. A "chamber members → HubSpot company records" scenario is about four steps.
- **HubSpot / Salesforce / Pipedrive** — map `name` → company, `domain` → domain (the deduplication key both CRMs use), `phone`, `address`, `category` → industry, `chamber` → lead source.
- **Postgres / BigQuery / Snowflake** — pull `https://api.apify.com/v2/datasets/<datasetId>/items?format=csv` and `COPY` it. `chamberHost` + `memberId` is a natural primary key.
- **Power BI / Tableau / Looker Studio** — connect to the same CSV endpoint; `state`, `city` and `category` are ready-made dimensions.
- **Webhooks** — attach an Apify webhook on `ACTOR.RUN.SUCCEEDED` to POST the dataset ID to your own service.
- **Schedules** — run Recipe 2 weekly and diff `memberId` for a new-member feed.

***

### 🆚 How this differs from our other Actors

| Actor | What it is | Why you would use it instead |
|---|---|---|
| **Chamber of Commerce Member Directory Scraper** *(this one)* | 927 **chambers of commerce** on `*.chambermaster.com`, many chambers per run, category + website enriched from each member's detail page | You want every local business in a metro or a state, filtered by category |
| **[GrowthZone Directory Scraper](https://apify.com/scrapersdelight/growthzone-directory-scraper)** | **Trade associations** on `*.growthzoneapp.com`, one directory per run, walked via the 26 alpha-index pages | You want to watch **one** association and be alerted on new joiners (Slack / webhook / email) |
| **[Decision-Maker Email Finder](https://apify.com/scrapersdelight/decision-maker-email-finder)** | Takes a domain, returns named decision-makers and their email addresses | You have this Actor's `domain` column and now need someone to email |
| **[Local Business Contact Enricher](https://apify.com/scrapersdelight/local-business-enricher)** | Takes a business website, returns name, role and contact details from it | You want contact detail added to a chamber list without leaving Apify |
| **[BBB Scraper](https://apify.com/scrapersdelight/bbb-scraper)** | Better Business Bureau profiles — accreditation, ratings, phones | You want an accreditation and complaint signal rather than chamber membership |
| **[BuildZoom Scraper](https://apify.com/scrapersdelight/buildzoom-scraper)** · **[Houzz Pro Scraper](https://apify.com/scrapersdelight/houzz-pro-scraper)** · **[California Contractor License Scraper](https://apify.com/scrapersdelight/cslb-contractor-scraper)** | Contractor-specific leads, licences, bonds and permits | Your ICP is construction trades specifically, and you want licence data a chamber never publishes |

Rule of thumb: *want every business in a metro?* this one. *Want to watch one association for new joiners?* GrowthZone. *Want an email address?* run this one, then the Decision-Maker Email Finder on the `domain` column.

***

### ⚖️ Comparison with the alternatives

| | This Actor | Copy-pasting the directory | Your own scraper | A bought SMB list |
|---|---|---|---|---|
| Setup | Click Try for free, press Start | — | A day, plus two page templates to handle | Sales call |
| Chambers covered | 927 bundled + any host you paste | One at a time | Whatever you code | Unknown provenance |
| Business category | 96–97% | Only by opening every member page | Only if you fetch detail pages too | Usually a coarse SIC/NAICS guess |
| Freshness | Live page, every run | Live but slow | Live | Months old |
| Cost per 1,000 | **$4** | Hours of somebody's time | Your time + maintenance | Often $100+ |
| Maintenance | Ours | — | Yours, on two skins | — |
| Coverage check before paying | **Free** (`listChambers`) | — | — | No |

***

### ❓ FAQ

**What exactly is one row?**
One chamber member business, with the chamber it belongs to. 100 rows = 100 businesses = $0.40.

**Does this need an account, a login or an API key?**
No. ChamberMaster member directories are public server-rendered pages. The Actor logs into nothing and collects no data behind any authentication.

**Do I need a proxy?**
No, and it is off by default. Measured: 1,033/1,034 hosts answered on a direct connection, 927/927 on a same-day whole-corpus re-fetch, and 1,536/1,537 requests succeeded through Apify's datacenter proxy (99.9%). There is no anti-bot to defeat here.

**How do I find my chamber's host?**
Open your chamber's *Member Directory*. If the address bar shows `something.chambermaster.com`, `something` is the host. Paste `something`, `something.chambermaster.com` or the whole URL — all three work. Or just run discovery mode (free) and search the list.

**Can I scrape a chamber that isn't in your 927?**
Yes. Put its host or URL in `chamberHosts`. The bundled 927 are the ones verified live on 2026-08-12; any ChamberMaster host works.

**Is my chamber covered? Can I check before I pay?**
Yes — that is what discovery mode is for. `{"listChambers": true}` returns all 927 with names, hosts and member counts in under 5 seconds, and the run records zero charged events.

**Do I get email addresses?**
No. ChamberMaster hides member email behind a JavaScript contact form; there is no `mailto:` to scrape and nobody can get one from these pages. Use the `domain` column (89.0% fill) with our Decision-Maker Email Finder, or any email-finder you already pay for.

**Can I get the whole catalogue in one run?**
Yes: `{"hostPreset": "all", "maxItems": 482110}`. That is every member of all 927 chambers, about $1,928, and a long run. Most people take one state or one metro instead. (`maxItems` has a minimum of 1, so there is no "unlimited" value — you always state your ceiling.)

**Do I get charged for rows my filters throw away?**
No. You are billed per row *returned*, on the `member-scraped` event. A category sweep may read 200,000 members to give you 3,000 — you pay for 3,000.

**Two runs — will I get duplicates?**
Within a run, no: 0 duplicates across 14,083 contiguous cards from 8 complete directories, deduplicated on `memberId` before anything is fetched. Across runs there is no memory, so re-running the same input returns the same members. Diff on `chamberHost` + `memberId`.

**Why is my run slow?**
Two deliberate reasons. A chamber publishes its *entire* directory in one page (the largest measured is 18.6 MB) because that is what guarantees every member and no duplicates. And each member's detail page is what fills `category` and `website`. A 1,000-row enriched run across 40 chambers measured about 7 minutes; a 200-row run across 8 large chambers measured 5 minutes 11 seconds. `enrichDetails: false` roughly halves it.

**Does turning off detail pages make it cheaper?**
No — the price per member is identical. It only trades run time for the category and website columns.

**Can I filter to one city?**
Yes, `cities` takes exact city names as the chamber publishes them (case-insensitive). Remember a chamber's members are not all in its own city, so this is genuinely useful.

**Can I monitor a chamber for new members?**
Attach an Apify Schedule and diff `memberId` between runs. For built-in new-joiner alerting on a single association, use our GrowthZone Directory Scraper.

**Will a run ever succeed with zero rows?**
No. Zero rows throws an error that names the scope and the dead-host count. A green run always means real rows.

**Something looks wrong — how do I debug it?**
Read the run log first: it lists every host, its live member count, how many detail pages were fetched, how many rows were kept, and the measured field fill. That is usually enough to tell an over-narrow filter from a site change. If it is a site change, open an issue.

**Is the data US-only?**
No. The corpus includes Canadian chambers and boards of trade (Halifax, Surrey & White Rock, Charlottetown and others), so `state` carries provinces and `zip` carries Canadian postal codes.

***

### ⚖️ Legal and fair use

This Actor reads **public business listings** — company name, business category, business phone, website and business address that chambers publish specifically so the public can find their members. It does not log in, it does not solve CAPTCHAs, and it collects **no contact-person names and no email addresses**.

Two things to know and judge for yourself:

1. `chambermaster.com` publishes a `robots.txt` that disallows crawling. There is no technical block — this is a terms-of-service question, and it is yours to weigh.
2. However you obtain business data, you remain responsible for complying with marketing and anti-spam law — **CAN-SPAM, CASL, TCPA, GDPR, PECR** — when you contact anyone on the list. Review the individual chamber's terms before commercial use.

Rows describe **businesses**, but a sole trader's business phone or address can also be personal data in some jurisdictions; treat the output accordingly.

ChamberMaster® and GrowthZone® are trademarks of their owner. This Actor is not affiliated with, endorsed by, or sponsored by them or by any chamber of commerce.

***

### 📝 Changelog

| Date | Change |
|---|---|
| **2026-08-13** | Free discovery mode (`listChambers`); every one of the 927 chambers' own names measured and shipped inside the Actor; `chamberPicker` dropdown of the 200 largest chambers by name; `states` converted to a 65-entry dropdown; `recordType` added to every row; input schema grouped into five sections. Field fill, duplicate rate and the transport ladder re-measured. The `states` filter now maps full state and province names ("Illinois", "British Columbia") back to their code, so the ~3% of rows that publish a name instead of a code are no longer silently dropped. **Last verified against the live site: 2026-08-13.** |
| 2026-08-12 | First release. 1,034 candidate hosts fetched, 927 verified live, 482,110 members counted; a–z completeness cross-check; both ChamberMaster skins parsed. |

***

### 💬 Support

Missing a chamber, want a filter that isn't here, or found a field that should be? **Open an issue on the Issues tab** — it gets read, and the host list gets re-verified.

If this saved you an afternoon of copy-pasting, **a 5-star rating helps other people find it**.

***

**Keywords:** chamber of commerce scraper · chamber of commerce member directory · chamber of commerce member list · chambermaster scraper · chambermaster.com · growthzone scraper · micronet directory · local business directory scraper · small business leads · SMB lead list · B2B lead generation · local business database · business directory export · chamber member export CSV · business phone numbers · business website list · businesses with no website · local business email list · merchant services leads · insurance agency leads · payroll leads · PEO leads · MSP leads · commercial insurance prospecting · restaurant leads · roofing contractor leads · dental practice leads · accountant leads · Kansas City chamber of commerce members · Memphis chamber members · Fullerton chamber · Manatee chamber · Halifax chamber of commerce · Lubbock chamber · Bryan-College Station chamber · Charlottetown chamber · board of trade directory · trade association member list · economic development data · local market research · CRM enrichment · HubSpot company import · Salesforce lead import · geo-targeted lead list · state business list · city business list · lead list by category

# Actor input Schema

## `listChambers` (type: `boolean`):

Tick this and the run skips scraping entirely: it returns one row per bundled chamber (rank, chamber name, host, member count, directory URL) so you can check yours is covered before spending anything. These rows are pushed WITHOUT the billing event, so a discovery run costs $0 in Actor charges. Then copy the 'host' value into 'Specific chambers' below and run for real.

## `hostPreset` (type: `string`):

Which slice of the bundled list to sweep when 'Chamber picker' and 'Specific chambers' are both empty. The default is the 2-chamber sample (Kansas City + Memphis), which returns 100 rows in about 10 seconds, so a run started with no input at all still finishes fast and cheap. The bigger presets are ordered largest-first: metro50 is the 50 biggest directories, all = every one of the 927 chambers verified live on 2026-08-12 (482,110 members). The biggest chambers are also the slowest to answer (the largest one replies on a ~21 s server-side delay per page), so give metro50 / metro200 / all a run timeout of an hour or more. This never overrides a chamber you named yourself.

## `chamberPicker` (type: `array`):

Pick chambers from a list instead of guessing a subdomain. Each entry shows the chamber's own name and its measured member count, e.g. 'Greater Memphis Chamber — 1,537 members'. Picking here overrides the preset above. Only the 200 largest are listed — for any of the other 727, use 'Specific chambers' below, or run discovery mode first.

## `chamberHosts` (type: `array`):

Any ChamberMaster chamber, bundled or not. Accepts the short host ('kcchamber'), the full host ('kcchamber.chambermaster.com') or a pasted directory URL ('https://kcchamber.chambermaster.com/list') — all three work. To find yours: open your chamber's Member Directory; if the address bar reads \*.chambermaster.com, the first label is the host. Overrides the preset. CLEAR THIS FIELD to sweep the preset instead.

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

Strongly recommended, and on by default. The chamber's listing page carries phone and postal address but NOT the business category, and it shows a website for only 8.5% of members (41,219 links across all 482,110 rows). Opening each member's own detail page takes category from 0.0% to 96.0% fill and website from 12.8% to 86.3%, measured on a 1,000-row run across 40 chambers. Turning it off makes a run roughly 2x faster and ships an empty category column. The price per member is identical either way — this only trades run time for fields.

## `categoryContains` (type: `array`):

Keep only members whose business category, name or description contains one of these words — case-insensitive substring, any one match keeps the row. Real chamber category strings look like 'Insurance', 'Roofing Contractors', 'Accountants — Certified Public'. Empty = every member. Needs 'Open each member's detail page' on, otherwise there is no category to match against and only name/description are searched.

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

Keep only members whose published address is in these states or provinces. Empty = everywhere. Note that a chamber's members are NOT all in one state: big-city chambers carry out-of-state corporate members (Kansas City's list includes a Southwest Airlines address in Dallas), so this filter is how you get a tight geography. Matched against what the chamber publishes, with full state and province names ('Illinois', 'British Columbia') mapped back to their code first. Measured state fill is 96-98%, and about 3% of rows publish something that is not a state at all (a country, or a stray postal code) — both are dropped by this filter.

## `cities` (type: `array`):

Keep only members in these cities. Free text because chambers publish city names in their own words — match is case-insensitive but otherwise exact, so 'Kansas City' works and 'KC' does not. Empty = every city. City fill is 98.1%; a member with no published city is dropped by this filter.

## `withPhoneOnly` (type: `boolean`):

Drop members with no published phone. Measured phone fill is 96.7% with detail pages on, so this normally removes about 1 row in 30 — it is a cheap safety net for a dialling list, not a big filter.

## `withWebsiteOnly` (type: `boolean`):

Drop members with no published website. Website fill is 86.3% with detail pages on and 12.8% with them off, so leave detail pages ON if you use this. Flip the logic for the opposite pitch: leaving this OFF is how web agencies find the ~14% of chamber members with no website at all.

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

Hard cap on the number of member rows returned — and therefore on what you are charged. Filters run across the WHOLE directory first, so this never cuts a search short: asking for 40 insurance members from Memphis still walks all 1,537 members to find the 33 that match. 100 is the default and a cheap first look ($0.40). 200 = $0.80. 1,000 = $4. All 927 chambers end to end = 482,110 members = about $1,928.

## `maxMembersPerHost` (type: `integer`):

Spread the budget across many chambers instead of exhausting the first one: 25 with a 1,000 cap gives you 25 members each from 40 chambers. 0 = no per-chamber limit, i.e. take one chamber to the end before moving on. Use it with a preset for a wide regional sample; leave it 0 when you want one chamber's complete roster.

## `detailConcurrency` (type: `integer`):

How many member detail pages are fetched in parallel, 1-16. 8 is the default and what every published measurement on this page was taken at. Lower it if you want to be gentler on one small chamber's site; raising it does not change the price, only the wall-clock time.

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

Not needed, and off by default. ChamberMaster serves plain server-rendered HTML with no anti-bot: measured 1,033/1,034 hosts answered HTTP 200 on a direct connection, 927/927 again on a same-day whole-corpus re-fetch, and 1,536/1,537 requests succeeded through Apify's datacenter proxy (99.9%). Proxying a clean host only adds a way to fail, so set one here only if you specifically need a fixed egress IP.

## Actor input object example

```json
{
  "listChambers": false,
  "hostPreset": "sample",
  "chamberPicker": [],
  "chamberHosts": [
    "kcchamber",
    "https://memphischamber.chambermaster.com/list"
  ],
  "enrichDetails": true,
  "categoryContains": [
    "insurance",
    "roofing",
    "dental"
  ],
  "states": [],
  "cities": [
    "Kansas City",
    "Overland Park"
  ],
  "withPhoneOnly": false,
  "withWebsiteOnly": false,
  "maxItems": 100,
  "maxMembersPerHost": 50,
  "detailConcurrency": 8,
  "proxyConfiguration": {}
}
```

# Actor output Schema

## `items` (type: `string`):

One row per chamber member business: name, category, phone, website, street, city, state, ZIP and the chamber it belongs to.

# 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 = {
    "listChambers": false,
    "hostPreset": "sample",
    "chamberPicker": [],
    "chamberHosts": [
        "kcchamber",
        "memphischamber"
    ],
    "enrichDetails": true,
    "maxItems": 100,
    "maxMembersPerHost": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/chamber-directory-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 = {
    "listChambers": False,
    "hostPreset": "sample",
    "chamberPicker": [],
    "chamberHosts": [
        "kcchamber",
        "memphischamber",
    ],
    "enrichDetails": True,
    "maxItems": 100,
    "maxMembersPerHost": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/chamber-directory-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 '{
  "listChambers": false,
  "hostPreset": "sample",
  "chamberPicker": [],
  "chamberHosts": [
    "kcchamber",
    "memphischamber"
  ],
  "enrichDetails": true,
  "maxItems": 100,
  "maxMembersPerHost": 50
}' |
apify call scrapersdelight/chamber-directory-scraper --silent --output-dataset

```

## MCP server setup

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