# Brownbook Scraper \[From $2💰] | 44M+ Business Data (`ahmed_jasarevic/brownbook-scraper`) Actor

Scrape 44M+ global business listings from brownbook.net: name, address, phone, email, website, categories, hours, reviews, ratings, social links and claim status. Public sitemap tree and a challenge-free JSON API - no browser, no CAPTCHA.

- **URL**: https://apify.com/ahmed_jasarevic/brownbook-scraper.md
- **Developed by:** [Ahmed Jasarevic](https://apify.com/ahmed_jasarevic) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.90 / 1,000 results

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

## Brownbook Scraper — Scrape 44M+ Global Business Listings As brownbook.net Data

Scrape **44M+ global business listings** from [brownbook.net](https://www.brownbook.net) — names, addresses, phones, emails, websites, socials, categories, opening hours, photos, videos, reviews, ratings and claim status — as structured brownbook.net data you can export and schedule. Built for lead-gen agencies, citation-building services, business data aggregators and sales teams that need a B2B contact database without a ZoomInfo or D\&B contract.

### Main Use Cases For B2B Lead Lists And Local SEO Citation Data

- **B2B lead list / sales intelligence data** — one row per company with phone, mobile, email, website and socials, ready to qualify.
- **NAP data for local SEO citation building** — `name`, `address`, `phone`, `website`, `city` / `state` / `zipCode`, plus `claimed`, `claimVerified`, `claimEndDate` to find listings that still need claiming.
- **Global business database / business data aggregator feeds** — the full 92-field record including NAICS business data (`naicsCode`, `naicsTitle`, `naicsBreadcrumb`), categories, tags, services, payment options.
- **Company email phone database** — `email`, `emails`, `additionalEmails`, `phone`, `mobile`, `voip`, `additionalPhones` (see Legal on outreach).
- **CRM enrichment** — pass known IDs to `businessIds`, get one rich record back per ID, no crawling.
- **Change monitoring** — schedule the highest leaf sitemaps (most recently updated listings) and diff `updatedAt` / `lastmod`.

### Build A Global Business Database Without Official API Access

brownbook.net is a Next.js site on Vercel behind Cloudflare. Its **search** UI is CAPTCHA-gated, but its **sitemap tree and JSON API are not**. This Actor enumerates IDs from the public sitemap tree — sitemap index → 31 index files → ~46,531 leaf sitemaps of 1,000 URLs each — then reads `GET https://api.brownbook.net/app/api/v1/businesses/resource/<id>` for the complete 92-field record as plain JSON. Extraction runs on CheerioCrawler: pure HTTP, no browser, no session pool, no cookies, no API key, no CAPTCHA solver. Reviews land in a second dataset, `brownbookreviews`. Endpoints, rejected alternatives and measured costs are below.

### Input Parameters

| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| `sitemapIndex` | string (select) | No | `"1"` | Index file `"1"`…`"31"`; each lists 1,501 leaf sitemaps (~1.5M businesses). Comma-separated lists work over the API (`"2,3,4"`). |
| `sitemapLeafCount` | string (select) | No | `"1"` | Leaf sitemaps to expand; 1 leaf = 1,000 business URLs. Presets `"1"`…`"1501"` = 1,000 … 1,501,000 businesses. |
| `businessIds` | string | No | `""` | Comma/space-separated numeric IDs fetched in full, e.g. `"53562541, 53562540"`. Skips discovery. |
| `startUrls` | array | No | `[]` | Explicit leaf sitemaps (`.../sitemap/50000.xml`) or `resource/<id>` records instead of default discovery. |
| `countrySample` | boolean | No | `false` | 30-row sample + total count per country from `https://api.brownbook.net/businesses?country=<cc>` (no pagination — coverage stats). |
| `countries` | string | No | `"us,gb,de,au,ca,fr"` | Comma-separated ISO codes; only used in sample mode. |
| `detail` | boolean | No | `true` | `true` = 92-field record per ID; `false` = sitemap IDs only (cheapest). |
| `includeMedia` | boolean | No | `true` | Fetch photo/video URLs; +1 request per business. |
| `includeReviews` | boolean | No | `true` | Write reviews to `brownbookreviews` (business rows keep `rating` + `reviewCount`). Embedded reviews free; possible extras cost up to 5 requests. |
| `maxReviewsPerBusiness` | integer | No | `0` | Cap review rows per business — `0` = all, hard max `25` (5 pages × 5). Lowers `Review` charges and skips comments calls once hit. |
| `maxItems` | integer | No | `5000` | Hard cap on business rows (1–250,000). Free accounts always capped at 50; review rows never consume this budget. |
| `proxy` | object | No | `{ "useApifyProxy": false }` | Standard Apify proxy picker; applies to sitemap, JSON API and enrichment requests. |

> Legacy inputs still work: `sitemapLeaves` is read as `sitemapIndex` and `maxLeafSitemaps` as `sitemapLeafCount`.

#### Modes

| Mode | Trigger | Output |
|---|---|---|
| **Sitemap** (default) | `sitemapIndex` / `sitemapLeafCount` set | Full business records from the sitemap tree |
| **By ID** | `businessIds` set | One rich record per ID — best for enrichment |
| **Start URLs** | `startUrls` set | Whatever URLs you pass (leaf sitemaps or `resource/<id>`) |
| **Country sample** | `countrySample: true` | 30-row sample per country + `totalFound` counts |
| **Taxonomy** | nothing set | Country coverage, top categories, full category taxonomy |

### Output Fields

| Dataset | Contains |
|---|---|
| **default** | Business records plus meta rows (`run_summary`, `sitemap_index`, …) |
| **`brownbookreviews`** | One row per review (business rows keep only `rating` and `reviewCount`) |

The reviews dataset is an **alias** declared in `.actor/actor.json` (`storages.datasets.brownbookreviews`) and opened with `Actor.openDataset({ alias })`, so the platform creates it at run start with its own schema and the **Reviews** view and lists it in the run's **Storage tab** beside the default dataset (the Output tab carries a direct *Reviews* link). `includeReviews: false` leaves it untouched. Aliases allow letters, digits and underscores while dataset names reject underscores — hence lowercase `brownbookreviews`.

Default-dataset rows are tagged with `type`: `business` (full listing, fields below), `business_sample` (30-row country sample), `country_coverage` (per-country record counts), `taxonomy` (category/country taxonomies), `sitemap_index` (the discovered sitemap tree), `business_url` (sitemap IDs only, `detail: false`), `run_summary` (run configuration).

#### `business` fields

**Identity & claim status** — `id`, `legacyBusinessId`, `name`, `shortName`, `slug`, `url`, `claimed`, `claimVerified`, `claimEndDate`, `approvedAt`, `businessProfileCompleted`, `profileCompleted`, `subscribed`, `userId`, `duplicateOf`, `closed`, `closedDate`

**Location** — `country`, `countryCode`, `state`, `city`, `area`, `address`, `addressLine1`, `addressLine2`, `addressLine3`, `addressExtra`, `zipCode`, `postcode`, `serviceArea`, `lat`, `lng`

**Contact** — `phone`, `mobile`, `voip`, `fax`, `email`, `emails`, `website`, `websites`, `websitesHost`, `additionalPhones`, `additionalWebsites`, `additionalEmails`

**Social links** — `socials` (map), `socialsPresent`, `facebook`, `twitter`, `instagram`, `linkedin`, `tiktok`, `youtube`, `skype`, `blog`

**Classification** — `categories`, `category`, `naicsCode`, `naicsTitle`, `naicsBreadcrumb`, `naicsConfidence`, `tags`, `services`, `products`, `specials`, `brands`, `associations`, `languages`, `paymentOptions`, `yearEstablished`

**Content & media** — `description`, `businessHours` (day → intervals), `hoursDisplay`, `specialOffers`, `logoUrl`, `imageCount`, `images`, `videoCount`, `videos`, `rating`, `reviewCount`

**Meta** — `createdAt`, `updatedAt`, `indexedAt`, `lastmod`, `scrapedAt`, `source`

#### Meta rows and views

Non-business rows carry `totalFound` (per-country counts), `indexCount` (31), `totalLeafSitemaps` (~46,531), `estimatedBusinessUrls` (~46.5M), `data` (raw payload), `mode` (which of the five modes ran) and `isPaying`. Two views ship with the dataset: **Businesses** (`name`, `city`, `countryCode`, `category`, `phone`, `email`, `website`, `claimed`) and **Contact leads** (`name`, `phone`, `mobile`, `email`, `website`, `facebook`, `instagram`, `linkedin`, `url`).

#### `brownbookreviews` fields

`type` (`review`), `reviewId`, `businessId`, `businessName`, `businessUrl`, `businessCountryCode`, `rating`, `comment`, `status`, `authorId`, `author`, `authorCountry`, `authorAvatar`, `mediaUrl`, `reply`, `createdAt`, `updatedAt`, `source`, `scrapedAt`.

> `reviewId` (not `id`): `id` is a reserved keyword when declared in a dataset schema — the platform rejects it with `NOT SUPPORTED: keyword "id", use "$id"`.

### Example Input And Output

**First 5,000 US listings** (5 leaf sitemaps): `{ "sitemapIndex": "1", "sitemapLeafCount": "5", "maxItems": 5000 }`

**Enrich specific businesses:** `{ "businessIds": "53562541,53562540,53562539" }`

**Cheapest pass — IDs only, no detail calls:** `{ "detail": false, "maxItems": 20 }`

**Targeted leaf sitemaps (recently-updated listings):** `{ "startUrls": ["https://www.brownbook.net/sitemap/46000.xml"] }`

**Country coverage statistics:** `{ "countrySample": true, "countries": "us,gb,de,au,ca,fr,de,it" }`

**A `business` row:**

```json
{
  "type": "business",
  "id": 53562541,
  "name": "Riverside Dental Studio",
  "url": "https://www.brownbook.net/business/53562541/riverside-dental-studio/",
  "claimed": true,
  "claimVerified": true,
  "closed": false,
  "countryCode": "us",
  "city": "Chicago",
  "address": "1420 W Riverside Drive, Chicago, IL 60610",
  "zipCode": "60610",
  "lat": 41.9217,
  "lng": -87.6513,
  "phone": "+1 312 555 0142",
  "email": "reception@riversidedental.example",
  "website": "https://www.riversidedental.example",
  "category": "Dentist",
  "naicsCode": "621210",
  "rating": 4.7,
  "reviewCount": 18,
  "lastmod": "2026-09-18T07:31:12Z",
  "scrapedAt": "2026-10-07T09:14:22Z",
  "source": "brownbook"
}
```

**A `review` row from `brownbookreviews`:**

```json
{
  "type": "review",
  "reviewId": 88123456,
  "businessId": 53562541,
  "businessName": "Riverside Dental Studio",
  "rating": 5,
  "comment": "Seen on time, friendly team, clear pricing.",
  "author": "J. Moreno",
  "createdAt": "2026-06-12T15:02:10Z",
  "scrapedAt": "2026-10-07T09:14:22Z",
  "source": "brownbook"
}
```

### Brownbook API Endpoints And The Sitemap Enumeration Strategy

#### Endpoints used

| Purpose | Endpoint |
|---|---|
| Sitemap index (root) | `https://api.brownbook.net/sitemap_indexes.xml` |
| Index files (31) | `https://www.brownbook.net/sitemap/index_<n>.xml` |
| Leaf sitemaps (~46,531) | `https://www.brownbook.net/sitemap/<n>.xml` — 1,000 URLs each |
| **Business detail (the data source)** | `GET https://api.brownbook.net/app/api/v1/businesses/resource/<id>` |
| Photos & videos | `GET .../api/v1/businesses/resource/<id>/media` |
| Reviews (paginated, 5 per page) | `GET .../api/v1/business/<id>/comments?page=<n>` |
| Country coverage + counts | `GET .../api/v1/businesses/countries` |
| Top countries | `GET .../api/v1/businesses/countries/top` |
| Top categories | `GET .../api/v1/businesses/categories/top` |
| Full category taxonomy | `GET .../api/v1/uberall-categories` |
| City autocomplete | `GET .../api/v1/autocomplete/cities/?search=<q>` |
| Country sample (30 rows) | `GET https://api.brownbook.net/businesses?country=<cc>` |

#### Why this strategy

1. **Embedded JSON blob → not viable.** The homepage ships Next.js App Router RSC flight data (`self.__next_f.push`) with only shell markup — no `__NEXT_DATA__`, no `application/ld+json`, no listing data.
2. **Algolia / third-party search backend → none.** No `algoliasearch()`, Meilisearch, Elasticsearch, TypeSense or Coveo anywhere in the bundle.
3. **Internal JSON API → found.** The frontend bundle calls `baseURL: "https://api.brownbook.net/app"` with `api/v1/...` routes; `businesses/resource/<id>` returns the complete 92-field record as plain JSON with no cookies, no API key and no Cloudflare challenge.
4. **The CAPTCHA route is deliberately skipped.** `POST /app/api/v1/businesses/search` returns HTTP 422 `Please complete the CAPTCHA verification to continue`. This Actor never calls it.
5. **Sitemap tree → the enumeration key.** `robots.txt` publishes `Sitemap: https://api.brownbook.net/sitemap_indexes.xml`, expanding into 31 index files → ~46,531 leaf sitemaps → ~46.5M `<loc>https://www.brownbook.net/business/<id>/<slug>/</loc>` URLs. Every record is keyed by its numeric ID, so the corpus is reachable **without search**: sitemap for IDs, JSON API for data.

**Result: CheerioCrawler only** — no Playwright, no browser, no session pool, no CAPTCHA solver, no Chrome process to pay for, yet the Actor still defaults to **2 GB of RAM** (see below).

### Scrape Brownbook Business Listings At Scale: Cost, RAM And Pricing

- **1 request per business** for the full record — the API response is parsed straight from the crawl response, so it is not fetched twice. **+1 request** when `includeMedia` is on.
- **Reviews are free when embedded** in the detail record; only businesses that may have more (5+ embedded, or none embedded but a rating) hit `/business/<id>/comments`, capped at 5 pages of 5. Typical run: **2–3 requests per business**, worst case 7.
- **2,048 MB RAM by default** (range 256–4,096). Pure HTTP makes memory cheap in compute units, but on Apify the **CPU share grows with memory** and too little of it throttles the autoscaled pool to ~3 in-flight requests. Same 1,000-business crawl: **59 s / $0.06 at the 2 GB default**, **132 s / $0.08 at 1 GB**, **240 s / $0.10 at 512 MB** (100 businesses: ~5–9 s). **1 GB is the trap in the middle** — half the RAM halves the CPU share, so the run more than doubles and costs more; a faster run is usually the cheaper run.
- **Concurrency capped at 8** (run time scales almost linearly with it), retries capped at 3, 90 s per-request timeout. Raising the cap to 16 on the same crawl gave **no gain (69 s vs 59 s)**: the pool settles at ~7–8 in flight, because per-request latency and CPU, not the cap, are the limit. `maxRequestsPerCrawl`, `maxItems`, concurrency and timeouts are all hard-capped.
- **Free accounts are capped at 50 businesses**; paying accounts are unlimited up to `maxItems` (250,000).

#### Pay-per-event pricing

Reviews are billed separately from businesses: only items pushed to the **default** dataset are charged by the built-in `result` event, while reviews-dataset items are never billed automatically — which is why `billableReviews()` issues an explicit charge.

| Event | Billed when | Price |
|---|---|---|
| `result` | a business row is written to the default dataset | $0.002 (FREE) → $0.0019 (GOLD+) |
| `Review` | a review row is written to `brownbookreviews` | $0.001 → $0.0009 |
| `Actor Start` | the run starts | $0.00005 |

Setup — Apify Console → this Actor → **Monetization** → **Pay per event**: add a custom event named **`Review`** (it must match `REVIEW_EVENT` in `src/routes.ts`) with the per-row price; optionally enable the synthetic **`apify-default-dataset-item`** event to bill business rows too (the platform charges dataset writes, no code needed); set `minimalMaxTotalChargeUsd` so a run cannot start without covering one result.

`billableReviews()` in `src/main.ts` then behaves like this:

- Not on pay-per-event, or `Review` not registered → reviews pushed free and a one-off `[charge]` warning says the setup is missing.
- On pay-per-event → `Actor.charge({ eventName: 'review', count })` runs before each review batch and only `chargedCount` rows are pushed.
- Budget exhausted (`eventChargeLimitReached`) → remaining reviews are dropped and the platform stops the run.
- Any charging error → the batch is pushed unpaid and logged; billing trouble never loses data.

`maxItems` and the review charge budget are independent. `maxReviewsPerBusiness` is the cheapest lever on the review bill: a cap of `5` skips most comments calls and cuts `Review` charges directly.

### Integrations & Automation

- **Apify API** — every input field is a JSON property, so any run is one `POST` of the input object to the Actor's run endpoint (the Console's **API** tab pre-fills the exact URL and token); results come back from the dataset endpoint as JSON, CSV, Excel, XML, HTML or RSS.
- **Webhooks** — fire on run success or dataset item creation and push rows into your CRM, warehouse or lead queue; the reviews dataset can carry its own webhook.
- **Zapier and Make** — native Apify integrations, so a finished run can append rows to Google Sheets, Airtable, HubSpot or Slack without code.
- **Recommended schedule: weekly new-listing monitoring.** Brownbook is community-edited, so a recurring run beats one giant crawl — schedule weekly with `startUrls` on the highest leaf sitemaps (most recently updated listings) and a modest `maxItems`, then diff `updatedAt` / `lastmod`. Recurring schedules also improve Store recommendation signals.

#### Local Development

`npm ci` · `npm run build` (tsc) · `npm run lint` (eslint, zero warnings policy) · `npm test` (vitest: sitemap parsing + row building) · `npm run start:dev`. Local runs read input from `storage/key_value_stores/default/INPUT.json`, e.g. `{ "businessIds": "53562541,53562540", "maxItems": 10 }`. Set `APIFY_USER_IS_PAYING=1` to lift the 50-record free-account cap while testing; results land in `storage/datasets/default` and `storage/datasets/brownbookreviews`.

### Related Actors

- [brownbook-business-scraper](https://apify.com/happitap/brownbook-business-scraper) — worldwide business details from `startUrls`
- [brownbook-business-scraper](https://apify.com/scrapers_lat/brownbook-business-scraper) — Brownbook search by keyword, location and country
- [brownbook-email-scraper](https://apify.com/email_scraper/brownbook-email-scraper) — public Brownbook emails by keyword and location
- [yellowpages-scraper](https://apify.com/automation-lab/yellowpages-scraper) — Yellow Pages listings with phones, addresses, ratings, reviews
- [yelp-business-scraper](https://apify.com/cirkit/yelp-business-scraper) — Yelp listings by keyword, location or URL, no API key or browser

### FAQ

#### What is the best way to access Brownbook business listings at scale?

Enumerate IDs from the public sitemap tree, then read each record from the JSON API. The index expands into 31 files and ~46,531 leaf sitemaps of 1,000 URLs, every `<loc>` carrying the numeric ID; `GET .../businesses/resource/<id>` returns the 92-field record as plain JSON with no cookies, API key or challenge. The search route (`POST .../businesses/search`) returns HTTP 422 CAPTCHA verification and is never called.

#### Is there an official Brownbook API?

Yes for records, no for bulk export: `api.brownbook.net` record, media, comments, countries, categories and autocomplete endpoints answer as plain JSON without authentication (docs.brownbook.net documents the API), but the search route is CAPTCHA-gated (HTTP 422). Bulk access means the published sitemap tree plus the per-record API — this Actor's entire path.

#### What are the alternatives to Brownbook for global business data?

Store options: [brownbook-business-scraper](https://apify.com/happitap/brownbook-business-scraper), [brownbook-business-scraper](https://apify.com/scrapers_lat/brownbook-business-scraper) and [brownbook-email-scraper](https://apify.com/email_scraper/brownbook-email-scraper), plus [yellowpages-scraper](https://apify.com/automation-lab/yellowpages-scraper) and [yelp-business-scraper](https://apify.com/cirkit/yelp-business-scraper). Commercial benchmarks are ZoomInfo, D\&B, Infobel, Kompass, Yellow Pages and Yelp; this Actor is the pay-per-event route to Brownbook's 44M+ listings — the one that skips the CAPTCHA-gated search UI by enumerating the sitemap tree.

#### Can I export NAP data from Brownbook for local SEO citation building?

Yes — a core job of this Actor. Business rows carry `name`, `address`, `addressLine1/2/3`, `city`, `state`, `zipCode`/`postcode`, `country`/`countryCode`, `lat`/`lng`, `phone` and `website`; the **Contact leads** view flattens `phone`, `mobile`, `email`, `website` and socials, while `claimed`, `claimVerified` and `claimEndDate` show which listings still need claiming.

#### Are there cheaper alternatives to ZoomInfo and D\&B for global business contact data?

Directory-based sources are the usual answer, and this Actor bills per event instead of by subscription: `result` $0.002 (FREE) → $0.0019 (GOLD+) per business row, `Review` $0.001 → $0.0009, `Actor Start` $0.00005 per run. Free accounts are capped at 50 businesses; paying accounts go to `maxItems` (250,000).

#### Can I legally scrape Brownbook listings for a commercial project?

Only public surfaces: `robots.txt` allows `/` and the sitemap tree, and the disallowed `/ajax/`, `/account/*`, `/businesses/` and `/review/add/` are never touched — no login, no authentication bypass, no CAPTCHA solving. Compliance with Brownbook's terms and data-protection law (GDPR/CCPA, since rows include contact data) is your responsibility — see Legal.

***

### SEO Keywords

brownbook scraper, brownbook API, brownbook.net data, brownbook alternative, brownbook business listings, global business database, B2B contact database, company email phone database, citation building data, NAP data for local SEO, business directory data export, business data aggregator, sales intelligence data, B2B lead list, NAICS business data, Yellow Pages alternative, Yelp alternative for B2B, ZoomInfo alternative, D\&B alternative, Infobel alternative, Kompass alternative

### For AI Agents & LLM Apps

Machine-oriented reference for calling this Actor.

**Purpose** — enumerate brownbook.net business IDs from the public sitemap tree and return 92-field business records plus a separate reviews dataset.

**Minimal working input** (sitemap mode, trimmed to the free-account ceiling): `{ "sitemapIndex": "1", "sitemapLeafCount": "1", "maxItems": 50 }`

**Variant — enrichment mode** (no crawling, one record per known ID): `{ "businessIds": "53562541,53562540", "maxItems": 10 }`

**Output field list** — default dataset, grouped: identity/claim (`id`, `legacyBusinessId`, `name`, `shortName`, `slug`, `url`, `claimed`, `claimVerified`, `claimEndDate`, `duplicateOf`, `closed`); location (`country`, `countryCode`, `state`, `city`, `area`, `address`, `addressLine1/2/3`, `zipCode`, `postcode`, `serviceArea`, `lat`, `lng`); contact (`phone`, `mobile`, `voip`, `fax`, `email`, `emails`, `website`, `websites`, `additionalPhones`, `additionalWebsites`, `additionalEmails`); socials (`socials`, `socialsPresent`, `facebook`, `twitter`, `instagram`, `linkedin`, `tiktok`, `youtube`, `skype`, `blog`); classification (`categories`, `category`, `naicsCode`, `naicsTitle`, `naicsBreadcrumb`, `naicsConfidence`, `tags`, `services`, `products`, `paymentOptions`, `yearEstablished`); content/media (`description`, `businessHours`, `hoursDisplay`, `logoUrl`, `imageCount`, `images`, `videoCount`, `videos`, `rating`, `reviewCount`); meta (`createdAt`, `updatedAt`, `indexedAt`, `lastmod`, `scrapedAt`, `source`); meta rows (`type`, `totalFound`, `indexCount`, `totalLeafSitemaps`, `estimatedBusinessUrls`, `data`, `mode`, `isPaying`). Reviews dataset: `type`, `reviewId`, `businessId`, `businessName`, `businessUrl`, `businessCountryCode`, `rating`, `comment`, `status`, `authorId`, `author`, `authorCountry`, `authorAvatar`, `mediaUrl`, `reply`, `createdAt`, `updatedAt`, `source`, `scrapedAt`.

**Behaviors an agent should know**

- **Mode priority** — `businessIds`, `startUrls` or `countrySample: true` each trigger their own mode; otherwise sitemap discovery runs from `sitemapIndex` / `sitemapLeafCount`; with nothing set the run emits coverage/taxonomy rows only. `detail: false` returns sitemap IDs only, with no contact fields.
- **Bill-relevant defaults** — `includeMedia: true` adds 1 request per business, `includeReviews: true` writes review rows the `Review` event bills, `maxReviewsPerBusiness: 0` keeps every review (max 25 per business), `maxItems` defaults to 5000; free accounts stay hard-capped at 50 businesses and review rows never consume that budget.
- **`sitemapIndex` accepts a comma-separated list over the API** (`"2,3,4"`); `sitemapLeaves` and `maxLeafSitemaps` are accepted as legacy aliases.
- **Keep the 2 GB default memory** — CPU share scales with memory (1 GB measured 132 s / $0.08, 512 MB 240 s / $0.10, versus 59 s / $0.06 at 2 GB on the same 1,000 businesses). Concurrency is fixed at 8 (16 measured no gain), 3 retries, 90 s timeout.
- **`reviewId`, never `id`** — the platform rejects `id` in a dataset schema with `NOT SUPPORTED: keyword "id", use "$id"`.
- **Two datasets** — business rows in `default`, reviews in the alias `brownbookreviews`; read them separately.

**Billing model** — pay per event: `result` ($0.002 → $0.0019 per business row), `Review` ($0.001 → $0.0009 per review row), `Actor Start` ($0.00005 per run).

### Legal & Compliance Disclaimer

This Actor is an independent third-party tool. It is not affiliated with, endorsed by, sponsored by or connected with brownbook.net or its operator.

It accesses only what the site publishes for all user-agents: the public sitemap tree (`robots.txt` allows `/` and the sitemap; it disallows `/ajax/`, `/account/*`, `/businesses/` and `/review/add/`, none of which this Actor touches) and the public JSON record endpoints, which answer without cookies, credentials or an API key. No login, no authentication bypass, no CAPTCHA solving — the CAPTCHA-gated search endpoint (`POST .../businesses/search`, HTTP 422) is deliberately never called, and no browser is driven.

Output includes published business contact data — names, addresses, phone numbers, emails, websites and social profiles — plus third-party submitted review content. You are responsible for your own compliance with brownbook.net's terms of service and with applicable data-protection law (GDPR/CCPA where your use covers EU or California personal data), and this contact data must not be used for unsolicited commercial outreach in violation of applicable law. This Actor is a data-collection tool; it provides no legal advice and claims no particular use is permitted.

# Actor input Schema

## `sitemapIndex` (type: `string`):

The sitemap tree publishes 31 index files, each listing 1,501 leaf sitemaps of 1,000 business URLs (~1.5M businesses per index). Choose which index file to expand - run indexes 1, 2, 3... in parallel to split one huge job into batches.

## `sitemapLeafCount` (type: `string`):

How many leaf sitemaps to expand from the index above. One leaf sitemap = 1,000 business URLs, so the preset shows the exact number of businesses you will reach.

## `businessIds` (type: `string`):

Comma or space separated numeric Brownbook business IDs to fetch in full, e.g. "53562541, 53562540". Each ID returns one rich record via the JSON API. Leave empty for sitemap discovery.

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

Optional. Explicit sitemap or resource URLs to crawl instead of the default sitemap discovery flow. Leaf sitemaps look like https://www.brownbook.net/sitemap/50000.xml and single records like https://api.brownbook.net/app/api/v1/businesses/resource/53562541.

## `countrySample` (type: `boolean`):

Enable sample mode. Calls the legacy public endpoint https://api.brownbook.net/businesses?country=<cc> which returns a fixed 30-row sample plus the total record count per country. Useful for coverage/statistics, not bulk extraction (that endpoint has no pagination).

## `countries` (type: `string`):

Comma separated ISO country codes to sample, e.g. "us,gb,de,au,ca,fr". Only used when country sample mode is enabled.

## `detail` (type: `boolean`):

When true (recommended) each discovered business ID is resolved through the JSON API for the full 92-field record. When false only sitemap IDs/URLs are emitted (much cheaper, far less data).

## `includeMedia` (type: `boolean`):

Also call /businesses/resource/<id>/media for each business to collect photo and video URLs. Adds 1 extra request per business.

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

Collect reviews into the separate "brownbookreviews" dataset (business rows keep only rating and review count). Reviews already embedded in the detail record are free; businesses that may have more reviews are resolved through the paginated comments endpoint.

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

Cap how many review rows are stored for a single business (0 = keep all). One business can hold at most 25 reviews because the comments endpoint pages 5 reviews at a time and the Actor reads at most 5 pages, so values above 25 change nothing. Review rows live in the separate "brownbookreviews" dataset and are what the pay-per-event "review" charge bills, so a lower cap directly lowers cost - and it also skips the comments calls for businesses that already hit the cap.

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

Hard cap on business records pushed to the dataset. Free accounts are always capped at 50 regardless of this value. Review rows live in their own dataset and do not consume this budget.

## `proxy` (type: `object`):

Select proxies to be used by your crawler.

## Actor input object example

```json
{
  "sitemapIndex": "1",
  "sitemapLeafCount": "1",
  "businessIds": "53562541, 53562540",
  "countrySample": false,
  "countries": "us,gb,de,au,ca,fr",
  "detail": true,
  "includeMedia": true,
  "includeReviews": true,
  "maxReviewsPerBusiness": 0,
  "maxItems": 5000,
  "proxy": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `reviews` (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 = {
    "proxy": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ahmed_jasarevic/brownbook-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 = { "proxy": { "useApifyProxy": False } }

# Run the Actor and wait for it to finish
run = client.actor("ahmed_jasarevic/brownbook-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 '{
  "proxy": {
    "useApifyProxy": false
  }
}' |
apify call ahmed_jasarevic/brownbook-scraper --silent --output-dataset

```

## MCP server setup

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