# BizQuest Scraper $1💰 Businesses, Franchises & Asset Listings (`abotapi/bizquest-scraper`) Actor

Extract business-for-sale, franchise, and asset listings from BizQuest. Search by category, US state, asking price, or use BizQuest URLs. Returns 50+ fields, including asking price, location, photos, broker name and phone where available, ready for CRM, deal pipeline, or spreadsheet use.

- **URL**: https://apify.com/abotapi/bizquest-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Lead generation, Real estate, Automation
- **Features:** Limited permissions
- **Stats:** 10 total users, 2 monthly users, 94.3% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 listing results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

## BizQuest Scraper: Business-for-Sale Listings, Broker Contacts and Financials

BizQuest Scraper turns BizQuest into a clean deal-flow feed. Get the asking price, cash flow, EBITDA, category, location and the broker's full contact block (name, phone, brokerage, profile link) for every business-for-sale, franchise and asset listing. Search by category, US state and price band, or paste BizQuest search and category links directly. Turn on full detail mode for gross income, inventory, employee counts and reasons for selling, then export to JSON, CSV or Excel, or pull results straight into your app through the API.

### Why This Scraper?

- **50+ fields on every listing**, including the full broker contact block (name, phone, brokerage, profile link), something many quick scrapers skip.
- **Two ways to find listings.** Filter by category, US state, keyword and asking price, or paste any BizQuest search or category URL and skip the filters entirely.
- **Optional full detail mode.** Turn on `includeDetails` for about 30 more fields per listing: gross income, inventory, FF\&E, employee counts, reasons for selling, financing and year established.
- **No page limit by default.** `maxPages` defaults to unlimited, and the run stops on its own once the site's own reported total is reached. `maxListings` (default 20, `0` for unlimited) is the one cap you set.
- **Two different ways to keep a search current.** Resume one interrupted run from its run or dataset id, or turn on incremental mode to schedule the same search repeatedly and get back only what changed.
- **Nothing is dropped.** Every record keeps the complete source item under `raw`, so a field the site adds later still reaches your dataset without a code change.
- **Keeps working through refused connections.** BizQuest's edge refuses ordinary page requests intermittently, so the default connection is Residential (country US) and listing data is read through the site's own search service rather than the search web pages. When a connection is refused, the actor retries with a different one and, as a last resort, over a backup connection, logging plainly what happened. A run that could not load a single results page ends as failed with a clear message instead of an empty dataset.

### Use Cases

- **Business brokers and M\&A advisors:** track new listings and price movements across categories and states to feed a prospecting pipeline.
- **Buy-side searchers and search funds:** build a filtered deal-flow list by industry, price band or state, and monitor it on a schedule for new matches.
- **Market researchers and analysts:** measure asking price, cash flow and EBITDA trends across categories and regions over time.
- **Franchise resale trackers:** use the franchise fee and capital fields to watch franchise resale activity by category.
- **Lead generation for lenders and service providers:** use the broker contact fields to build outreach lists tied to active listings.

### Data You Get

> Sample shape, values are illustrative placeholders, not from a live listing.

| Field | Example |
| --- | --- |
| `id` | `20000001` |
| `title` | `"Sample Established Service Company"` |
| `url` | `"https://www.bizquest.com/business-for-sale/sample-listing/BW20000001/"` |
| `description` | `"Recurring-revenue service company with a stable client base."` |
| `askingPrice` | `750000` |
| `cashFlow` | `220000` |
| `ebitda` | `180000` |
| `category` | `"Home & Garden"` |
| `location` | `"Florida"` |
| `region` | `"FL"` |
| `stateName` | `"Florida"` |
| `isFranchise` | `false` |
| `initialFee` | `null` |
| `initialCapital` | `null` |
| `brokerName` | `"Jane Doe"` |
| `brokerCompany` | `"Sample Brokerage"` |
| `brokerPhone` | `"+10000000000"` |
| `brokerProfileUrl` | `"/business-broker/sample-brokerage/jane-doe/BW00000/"` |
| `image` | `"https://images.bizquest.com/shared/listings/000000000.jpg"` |
| `hotProperty` | `false` |
| `recentlyAdded` | `true` |
| `raw` | the complete source item for this listing |

Every record also carries, whenever BizQuest provides them, the listing's own fields promoted to the top level: `header`, `city`, `county`, `zip`, `streetAddress`, `employees`, `realEstate`, `homeBased`, `absenteeOwner`, `relocatable` and similar (a field BizQuest does not send is simply absent, never a placeholder). The numeric and text fields are type-checked before output, so a malformed upstream value becomes `null` rather than stopping the run. Every record also carries `images` (the full photo array), `locationCrumbs`, `categoryId`, `listingTypeId`, `leaseRatePerSquareFoot`, `realEstateIncludedInAskingPrice`, `financingTypeId`, the full `contactInfo` block (broker contact, verbatim) and `diamondMetaData` block, plus `sourceUrl` and `scrapedAt`. With `includeDetails` on, each record also gains `grossIncome`, `inventory`, `ffe`, `employeesFullTime`, `employeesPartTime`, `yearEstablished`, `buildingSquareFeet`, `summary`, `support`, `financingOptions`, `reasonsForSelling`, `competition`, `growth`, `fullDescription`, `leaseTerms` and the full detail object under `detailRaw`, and cash flow, gross income and EBITDA fill in wherever the seller discloses them on the detail page. With incremental mode on, every returned record also carries `changeType`, `changedFields`, `firstSeenAt` and `lastSeenAt`.

#### Dataset views

The dataset has four views in the Console: **Listings Overview** (price, cash flow, EBITDA, category, location, broker, change columns), **Broker Contacts**, **Listing Details** (the `includeDetails` fields) and **Changes** (incremental `changeType`, `changedFields`, first and last seen).

### How to Use

1. Pick a **mode**: `search` (build a filtered search from category, state, price band and keyword) or `url` (paste one or more BizQuest search or category links; filters are ignored in this mode).
2. For search mode, fill in the category or sub-category, locations, price, cash-flow, listing-age and year filters and keyword you want. For URL mode, paste the links.
3. Turn on **Fetch full listing details** if you want the extra financial and description fields.
4. Set **Max listings** to control run size and cost, then click **Start**.

**Search one state:**

```json
{
  "mode": "search",
  "locations": ["Florida"],
  "maxListings": 100
}
```

**Category plus state plus price band:**

```json
{
  "mode": "search",
  "category": "Food & Beverage",
  "locations": ["California"],
  "minPrice": 200000,
  "maxPrice": 800000,
  "includeDetails": true,
  "maxListings": 100
}
```

**Paste BizQuest URLs (filters are ignored in this mode):**

```json
{
  "mode": "url",
  "urls": [
    "https://www.bizquest.com/businesses-for-sale-in-florida-fl/",
    "https://www.bizquest.com/food-and-beverage-businesses-for-sale/"
  ],
  "maxListings": 200
}
```

#### Supported URL shapes (URL mode)

URL mode with no URLs pasted now runs the example list below (one real URL per supported shape) instead of stopping with an error. Pasted URLs on `m.bizquest.com` or plain `bizquest.com` are treated as `www.bizquest.com`.

| Shape | Example |
| --- | --- |
| Nationwide search | `https://www.bizquest.com/businesses-for-sale/` |
| Nationwide, paginated | `https://www.bizquest.com/businesses-for-sale/page-2/` |
| State search | `https://www.bizquest.com/businesses-for-sale-in-florida-fl/` |
| State, paginated | `https://www.bizquest.com/businesses-for-sale-in-florida-fl/page-2/` |
| City search | `https://www.bizquest.com/businesses-for-sale-in-miami-fl/` |
| County search | `https://www.bizquest.com/businesses-for-sale-in-miami-dade-county-fl/` |
| Top-level category, nationwide | `https://www.bizquest.com/food-and-beverage-businesses-for-sale/` |
| Category and state | `https://www.bizquest.com/food-and-beverage-businesses-for-sale-in-florida/` |
| Sub-category, nationwide | `https://www.bizquest.com/restaurants-for-sale/` |
| Sub-category and state | `https://www.bizquest.com/restaurants-for-sale-in-florida/` |
| State search with sort link | `https://www.bizquest.com/businesses-for-sale-in-florida-fl/?q=%3Ff_srt%3Dprice-asc` |
| Mobile host | `https://m.bizquest.com/businesses-for-sale-in-florida-fl/` |
| Broker profile (that broker's listings) | `https://www.bizquest.com/business-broker/transworld-business-brokers/james-knox/BW29511/` |

Also accepted, but not listed above because each one has the same target as a listed shape: the older `/buy-a-business-for-sale/search/` link (the site redirects it to the nationwide search, so it is read as `/businesses-for-sale/`) and a plain `?sort=price` query on a search link (the site ignores it and serves the same page as the search without it; use the `?q=` sort link above to actually sort). A broker directory (`/florida-business-brokers/`) is a page of brokers with no listings on it, and the broker-directory index `/business-brokers/` does not exist, so those are skipped with a warning.

A pasted single-listing URL (`/business-for-sale/<name>/BW<id>`) returns that listing. In a `?q=` link only the price or cash-flow sort is applied; any other filter inside `?q=` is not supported and is logged as a warning (the page is walked unfiltered). The default list includes one broker-directory example (`/florida-business-brokers/`) so every confirmed page shape is represented; it yields no listings and is skipped with a warning per URL, like franchise brand pages are skipped with a warning per URL.

**Recurring monitoring of the same search:**

```json
{
  "mode": "search",
  "locations": ["Florida"],
  "incrementalMode": true,
  "maxListings": 0
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/bizquest-scraper").call(run_input={"mode": "search", "locations": ["Florida"], "maxListings": 100})
for listing in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(listing["title"], listing["askingPrice"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/bizquest-scraper').call({ mode: 'search', locations: ['Florida'], maxListings: 100 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`) continues one specific interrupted run: paste its run id or dataset id and the actor skips every listing it already collected there, so this run returns only the delta and you don't pay twice.
- **Incremental mode** (`incrementalMode`) is for scheduled runs over the same search. Each listing is classified `NEW`, `UPDATED` (with `changedFields`), `UNCHANGED` (suppressed and not billed unless `emitUnchanged` is on), or `EXPIRED` (only after a run that scanned the whole tracked search with `emitExpired` on). A listing only comes back classified `REAPPEARED` after it was previously marked `EXPIRED` and is then found again on a later run. `stateKey` names or shares the stored state; otherwise it is derived automatically from your search filters, so different searches never mix state; changing `changeScope` or `ignoreFieldsForChanges` keeps the same state, so it never re-bills the catalogue. The scrape time, call-tracking phone numbers, ad-tier and sponsor ids, the result-rank offset, which result page it was found on, and the age badges (recently added, recently updated, hot property, price reduced) never count as a change, though they stay in the output. `changeScope` set to price and cash flow only makes just the asking price, cash flow, EBITDA and the price-reduced flag decide `UPDATED`. `compactUpdates` returns short `UPDATED` and `EXPIRED` rows (ids, url, title, current figures and `priceChanges` with old and new values); `NEW` rows stay complete and compact rows do not bill the detail-enrichment event. `expireAfterMissedScans` waits for N complete scans in a row before marking `EXPIRED`; `EXPIRED` means the listing was no longer found in the search, not proof that the business was sold. `ignoreFieldsForChanges` excludes more fields, with dotted names such as `raw.img` or `contactInfo.contactPhoto` for nested keys.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape, the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each record, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify > Settings > Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. If the picker is empty, you haven't authorized a connector yet.
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` builds a filtered search from the fields below, `url` walks pasted BizQuest URLs instead. |
| `category` | string | `(any)` | Industry category dropdown (search mode). Leave empty for all categories. |
| `locations` | array | none (prefill: `["Florida"]`) | US states by name or two-letter code. One search runs per state. Leave empty for nationwide. |
| `keyword` | string | none | Phrase to match in the listing title or description, applied after fetch. |
| `sortBy` | string | `(featured)` | Server-side order by price or cash flow, low to high or high to low. Ranks the whole result set before paging. Leave empty for the site's featured order. |
| `minPrice` | integer | none | Only keep listings at or above this asking price, in USD. Listings with no disclosed price are excluded when set. |
| `maxPrice` | integer | none | Only keep listings at or below this asking price, in USD. |
| `subCategory` | string | any | One BizQuest sub-category (for example Restaurants or Auto Body & Repair), applied through the site's own sub-category pages. It belongs to one parent category and replaces `category` when set. |
| `minCashFlow` | integer | none | Only listings with cash flow at or above this amount in USD, applied by BizQuest's own search before paging, so it narrows the whole result set. The cash flow shown on list pages is often hidden by the site; turn on `includeDetails` to see it where the seller discloses it. |
| `maxCashFlow` | integer | none | Only listings with cash flow at or below this amount in USD, applied the same server-side way as `minCashFlow`. |
| `listedWithinDays` | integer | none | Only listings first posted in the last N days (for example 7 for this week's new deals), applied server-side. |
| `establishedAfterYear` | integer | none | Only businesses established in or after this year, applied server-side. |
| `urls` | array | one example of each supported URL shape (listed in the URL shapes table) | BizQuest URLs for URL mode. Filter fields above are ignored in this mode. Left empty in URL mode, the example list is used. |
| `sessionJarSeed` | string (secret) | (empty) | One-time operator field: paste a cookie jar from a signed-in browser session; the actor stores and rotates it for broker-profile pages. Re-paste a fresh jar only if broker pages keep failing across many runs. Requires a 2048MB allocation. |
| `includeDetails` | boolean | `false` | Adds about 30 more fields per listing: gross income, inventory, FF\&E, full description, employees, reasons for selling, financing and year established. |
| `maxPages` | integer | `0` | Optional bound on result pages (50 listings each) walked per search or URL. `0` means walk the whole catalog and stop on its own at the site's reported total, an empty page, or a page that repeats content already served. Does not cap listing count; use Max listings for that. |
| `maxListings` | integer | `20` | The one cap on total listings collected (maximum 100000). `0` means unlimited. |
| `resumeFromRunId` | string | none | ID of a previous run of this actor (or a dataset ID). Listings already in that dataset are skipped, so this run returns only new listings, a delta. |
| `incrementalMode` | boolean | `false` | Turn on for recurring or scheduled monitoring of the same search. Returns only NEW and UPDATED listings by default, instead of the full result set every time. EXPIRED, and later REAPPEARED, appear only with `emitExpired` on. |
| `stateKey` | string | none | Names the incremental-mode monitoring campaign explicitly. Leave empty to let the actor derive a key automatically from the search filters. |
| `emitUnchanged` | boolean | `false` | Incremental mode only. Also return, and bill, listings that haven't changed, marked UNCHANGED. |
| `emitExpired` | boolean | `false` | Incremental mode only. Also return, and bill, listings no longer found, marked EXPIRED, produced only after a complete, uncapped, non-resumed scan. |
| `expireAfterMissedScans` | integer | `1` | Incremental mode only. Mark EXPIRED only after the listing is missing from this many complete scans in a row. |
| `changeScope` | string | `all` | Incremental mode only. `all` or `priceAndStock` (asking price, cash flow, EBITDA and price-reduced flag only). |
| `compactUpdates` | boolean | `false` | Incremental mode only. Short UPDATED and EXPIRED rows with `priceChanges`. |
| `ignoreFieldsForChanges` | array | none | Incremental mode only. Field names or dotted paths (for example `raw.img`) that never count as a change. |
| `proxy` | object | Residential US | Connection settings. Residential US is recommended and is the default. |
| `mcpConnectors` | array | none | Optional: send a summary of each record to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | none | Notion connector only: page under which item pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. Does not affect the dataset. |

### Output Example

> Sample shape, values are illustrative placeholders, not from a live listing.

```json
{
  "id": 20000001,
  "listNumber": 20000001,
  "title": "Sample Established Service Company",
  "url": "https://www.bizquest.com/business-for-sale/sample-listing/BW20000001/",
  "description": "Recurring-revenue service company with a stable client base.",
  "image": "https://images.bizquest.com/shared/listings/000000000.jpg",
  "images": ["https://images.bizquest.com/shared/listings/000000000.jpg"],
  "askingPrice": 750000,
  "cashFlow": 220000,
  "ebitda": 180000,
  "location": "Florida",
  "region": "FL",
  "stateName": "Florida",
  "category": "Home & Garden",
  "categoryId": 316,
  "isFranchise": false,
  "brokerName": "Jane Doe",
  "brokerCompany": "Sample Brokerage",
  "brokerPhone": "+10000000000",
  "brokerProfileUrl": "/business-broker/sample-brokerage/jane-doe/BW00000/",
  "contactInfo": { "contactFullName": "Jane Doe", "brokerCompany": "Sample Brokerage" },
  "hotProperty": false,
  "recentlyAdded": true,
  "sourceUrl": "https://www.bizquest.com/businesses-for-sale-in-florida-fl/",
  "scrapedAt": "2026-01-01T00:00:00.000000+00:00",
  "raw": { "header": "Sample Established Service Company", "price": 750000, "urlStub": "sample-established-service-company" }
}
```

### Plan Requirement

Residential proxy access is required. BizQuest's edge answers datacenter connections (and requests with no proxy) with an "Access Denied" refusal, so the default connection is Residential, country US, and you need an Apify plan that includes Residential proxy access (or your own residential proxy URLs). Keep the proxy group on Residential. If the default connection is refused, the actor logs: "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable." and, when the backup connection answers, "Served over the backup connection (the default connection was unavailable)." Pages can still be unavailable on the backup connection.

If no results page can be loaded at all, the run ends as failed (never a silent empty dataset) with one of these messages:

- "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required: https://apify.com/pricing. If you entered your own proxy URLs, check that their credentials are valid."
- "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested BizQuest pages. Upgrade at https://apify.com/pricing or supply your own proxy URLs, then try again."
- "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later."

Each listing request has a 25 second budget, so a stuck connection is abandoned and rotated instead of stalling the run. Listing data is read through BizQuest's own search service, which answers where the search web pages refuse connections; the search web pages are only used as a fallback. Broker profile pages require a maintained session, and seeding it is a one-time operator step: paste a fresh cookie jar into the sessionJarSeed input (or the BIZQUEST_SESSION_JAR environment variable) and the actor stores it, serves the broker pages, and keeps the session fresh for later runs. That path runs only when the run allocates at least 2048 MB of memory (set it in the run options when you paste broker profile URLs). The site refuses broker profile pages intermittently. This refusal comes and goes over the day, and a refused run fails fast with a message that says whether the site or the stored session was at fault. Re-running later often succeeds; if repeated runs keep failing, paste a fresh cookie jar into the sessionJarSeed input (search and individual-listing URLs are unaffected). Detail mode reads listing details through BizQuest's own listing data service; individual listing web pages and franchise brand pages are refused even on residential connections, so franchise brand links are skipped with a warning.

### FAQ

#### How much does it cost?

You pay per listing returned (the dataset-item event), plus the detail-enrichment event once per listing whose extra fields were fetched when `includeDetails` is on, plus the actor-start event. Listings suppressed as UNCHANGED, or skipped because of resume, are not charged. The **Pricing** tab shows the current rates. Use **Max listings** to cap the cost of any run.

#### Is it legal to scrape BizQuest?

This actor collects only publicly available business-for-sale listing data. You are responsible for how you use it: follow BizQuest's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Broker names and phone numbers are contact details the listing itself publishes; handle them accordingly.

#### Can I get only new or changed listings on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only new and updated listings, and unchanged ones are not billed. A quiet run that finds nothing changed still completes normally and simply returns zero rows, it is not an error.

#### Why did my incremental run return fewer rows than Max listings?

Under incremental mode, Max listings caps how many listings are scanned, not how many are returned. If most of what it scans is unchanged, it can legitimately return far fewer rows than the cap, or zero, on a quiet day.

#### Why did my run fail instead of returning an empty dataset?

A URL list where no URL is a supported shape, a `resumeFromRunId` that cannot be resolved to a run or dataset you own, or Incremental mode combined with `resumeFromRunId` on a search that already has saved state, all stop the run with a clear error message rather than a silent empty result. If the site simply returns no matching listings for your filters, or your connection is not being admitted reliably, the run completes and logs "No listings matched. Widen your filters or check your proxy connection, then try again." A run where not one page could be loaded fails with the connection messages listed under Plan Requirement.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more leads data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>📇 <a href="https://apify.com/abotapi/bizbuysell-scraper"><b>BizBuySell Scraper</b></a><br>Extract business-for-sale and franchise listings from bizbuysell.com with financials...</td><td>📇 <a href="https://apify.com/abotapi/businessesforsale-com-scraper"><b>BusinessesForSale.com Scraper</b></a><br>Extract businesses and franchises listed for sale on BusinessesForSale.com. Search by...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/crexi-commercial-listings-scraper"><b>Crexi Scraper</b></a><br>Scrape crexi.com commercial listings: asking price, cap rate, square footage, units, year...</td><td>🏨 <a href="https://apify.com/abotapi/loopnet-scraper"><b>LoopNet Scraper</b></a><br>Scrape LoopNet commercial listings across the US, including office, retail, industrial...</td></tr>
<tr><td>📇 <a href="https://apify.com/abotapi/google-maps-scraper"><b>Google Maps Scraper</b></a><br>Extract business data from Google Maps at scale. Get names, addresses, phone numbers...</td><td>🛒 <a href="https://apify.com/abotapi/flippa-com-scraper"><b>Flippa Scraper</b></a><br>Scrape Flippa listings with full enrichment, including revenue, profit, valuation...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/bizquest-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

How to find listings. 'search' builds filtered URLs from the fields below. 'url' walks BizQuest URLs you paste.

## `category` (type: `string`):

Industry category to filter by (search mode). Leave empty for all categories.

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

US states to search, by name or two-letter code (e.g. "Florida" or "FL"). One server-side filtered search runs per state. Leave empty for nationwide. City-level filtering is best done in URL mode.

## `keyword` (type: `string`):

Optional phrase to match in the listing title or description (applied after fetch).

## `sortBy` (type: `string`):

Server-side ordering of results (applied before pagination, so it ranks the whole result set). Default is the site's featured order.

## `minPrice` (type: `integer`):

Only keep listings at or above this asking price. Listings with no disclosed price are excluded when set.

## `maxPrice` (type: `integer`):

Only keep listings at or below this asking price.

## `subCategory` (type: `string`):

Narrow to one BizQuest sub-category (e.g. Restaurants, Auto Body & Repair). A sub-category belongs to one parent category and replaces the Category choice above. Applied server-side through the site's own sub-category pages.

## `minCashFlow` (type: `integer`):

Only listings whose disclosed cash flow is at or above this amount. Applied by BizQuest's own search server-side, so it narrows the whole result set before paging. The cash flow shown on the list page is often hidden by the site; turn on Fetch full listing details to see it where the seller discloses it.

## `maxCashFlow` (type: `integer`):

Only listings whose disclosed cash flow is at or below this amount (server-side, same as Min cash flow).

## `listedWithinDays` (type: `integer`):

Only listings first posted in the last N days (for example 7 for new deals this week). Applied server-side before paging.

## `establishedAfterYear` (type: `integer`):

Only businesses established in or after this year, e.g. 2015 (server-side).

## `urls` (type: `array`):

Paste BizQuest search, category, state, city, county or broker-profile URLs (nationwide, paginated page-N, mobile m. host and ?q= sort links included). Filter fields above are ignored in URL mode. Pagination continues from the page in the URL onward. A ?q= sort is applied; other ?q= filters, franchise pages and broker directories are skipped with a warning. Leave empty to use the example list.

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

When on, adds ~30 extra fields per listing: gross income, inventory, FF\&E, full description, employees, reasons for selling, financing, year established, and more. Runs concurrently and adds little time or cost.

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

Optional bound on result pages (50 listings each) walked per search/URL. Use 0 to walk the whole catalogue: the run stops on its own once the site's reported total is reached, a page comes back empty, or a page returns no new listings. This does NOT cap the number of listings; use Max listings for that.

## `maxListings` (type: `integer`):

Hard cap on listings across all searches (the main limit). Set 0 for no limit at all.

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

BizQuest reliably admits US Residential connections; datacenter and free-tier runs are admitted only intermittently and may return few results. Residential US is recommended.

## `resumeFromRunId` (type: `string`):

Optional. ID of a previous run of this actor (or a dataset ID). Listings already in that dataset are skipped, so this run returns only NEW listings (a delta). Combine both runs' datasets for the full set. Max listings then counts only the new listings. For recurring daily/weekly monitoring of the same search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns all matching listings as NEW. Later runs compare every listing against the last run (asking price, cash flow, EBITDA, title, description, location, category, broker details, photos and the other listing fields) and return only NEW, UPDATED and REAPPEARED listings, with an UPDATED row's changedFields naming what moved. Never compared, because they change with no real listing change: the scrape timestamp, call-tracking phone numbers, ad-tier and sponsor ids, the result-rank offset, and the age badges (recently added, recently updated, hot property, price reduced). Use What counts as a change to compare only price and cash-flow figures. Turn on "Emit unchanged" or "Emit expired" only when you also want those listings returned (and billed). State is kept separately for each category/location/keyword/sort/price/URL and detail-mode setup; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

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

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the search filters and detail setting — different searches then never mix state with each other.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return listings that have not changed since the last run, marked UNCHANGED. This returns — and bills — extra rows you already have, so leave it off unless you specifically want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return listings that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search — not when Max listings capped it, or when Resume or a mid-run checkpoint continuation was used. This returns — and bills — extra synthetic rows, so leave it off unless you need expiry tracking.

## `expireAfterMissedScans` (type: `integer`):

Only used with Emit expired listings. A listing is marked EXPIRED only after it has been missing from this many complete scans in a row (default 1). Raise it (for example 3) so a listing that briefly drops out of the search results is not reported as gone. A listing that comes back resets the count. EXPIRED means the listing was no longer found in the search; it does not prove the business was sold or delisted.

## `changeScope` (type: `string`):

All fields (default): any change to the listing record returns an UPDATED row, except the always-ignored fields named under Incremental changes. Price and cash flow only: only a change to the asking price, cash flow, EBITDA or the price-reduced flag returns an UPDATED row; edits to the description, photos, broker details or badges never do. Switching this option under a State key continues from the saved baseline without re-billing it; with the automatic key, a new scope starts a new baseline.

## `compactUpdates` (type: `boolean`):

Off by default. When on, UPDATED and EXPIRED rows are short: listing id, url, title, current asking price, cash flow and EBITDA, plus priceChanges showing the old and new value of each moved price field. NEW rows always carry the complete listing record. Detail enrichment is not billed for compact rows because they do not carry the detail data.

## `ignoreFieldsForChanges` (type: `array`):

Optional. Field names to exclude from UPDATED change detection, in addition to the built-in handling (scrape time, call-tracking phone, ad-tier ids, result rank and age badges never trigger an UPDATED). Use this if another field turns out to be noisy in your setup, for example a photo list that rotates. A dotted name ignores a nested key: raw.img, contactInfo.contactPhoto, diamondMetaData.regionId. The raw block mirrors the upstream listing, so to ignore a value completely, ignore both its top-level field and its raw copy. Ignored fields are still returned in the output; they only stop counting as a change.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## `sessionJarSeed` (type: `string`):

One-time operator field: paste a cookie jar exported from a signed-in browser session on the site (JSON, either \[{name,value}] or {name:value}). The first broker-profile run stores it and keeps it rotated in the actor's session store; later runs reuse the rotated jar. Requires a 2048MB memory allocation.

## Actor input object example

```json
{
  "mode": "search",
  "category": "",
  "locations": [
    "Florida"
  ],
  "sortBy": "",
  "subCategory": "",
  "urls": [
    "https://www.bizquest.com/businesses-for-sale/",
    "https://www.bizquest.com/businesses-for-sale/page-2/",
    "https://www.bizquest.com/businesses-for-sale-in-florida-fl/",
    "https://www.bizquest.com/businesses-for-sale-in-florida-fl/page-2/",
    "https://www.bizquest.com/businesses-for-sale-in-miami-fl/",
    "https://www.bizquest.com/businesses-for-sale-in-miami-dade-county-fl/",
    "https://www.bizquest.com/food-and-beverage-businesses-for-sale/",
    "https://www.bizquest.com/food-and-beverage-businesses-for-sale-in-florida/",
    "https://www.bizquest.com/restaurants-for-sale/",
    "https://www.bizquest.com/restaurants-for-sale-in-florida/",
    "https://www.bizquest.com/businesses-for-sale-in-florida-fl/?q=%3Ff_srt%3Dprice-asc",
    "https://m.bizquest.com/businesses-for-sale-in-florida-fl/",
    "https://www.bizquest.com/business-broker/transworld-business-brokers/james-knox/BW29511/",
    "https://www.bizquest.com/florida-business-brokers/"
  ],
  "includeDetails": false,
  "maxPages": 0,
  "maxListings": 20,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "expireAfterMissedScans": 1,
  "changeScope": "all",
  "compactUpdates": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `contacts` (type: `string`):

No description

## `detail` (type: `string`):

No description

## `changes` (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 = {
    "mode": "search",
    "locations": [
        "Florida"
    ],
    "urls": [
        "https://www.bizquest.com/businesses-for-sale/",
        "https://www.bizquest.com/businesses-for-sale/page-2/",
        "https://www.bizquest.com/businesses-for-sale-in-florida-fl/",
        "https://www.bizquest.com/businesses-for-sale-in-florida-fl/page-2/",
        "https://www.bizquest.com/businesses-for-sale-in-miami-fl/",
        "https://www.bizquest.com/businesses-for-sale-in-miami-dade-county-fl/",
        "https://www.bizquest.com/food-and-beverage-businesses-for-sale/",
        "https://www.bizquest.com/food-and-beverage-businesses-for-sale-in-florida/",
        "https://www.bizquest.com/restaurants-for-sale/",
        "https://www.bizquest.com/restaurants-for-sale-in-florida/",
        "https://www.bizquest.com/businesses-for-sale-in-florida-fl/?q=%3Ff_srt%3Dprice-asc",
        "https://m.bizquest.com/businesses-for-sale-in-florida-fl/",
        "https://www.bizquest.com/business-broker/transworld-business-brokers/james-knox/BW29511/",
        "https://www.bizquest.com/florida-business-brokers/"
    ],
    "maxPages": 0,
    "maxListings": 20,
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "expireAfterMissedScans": 1,
    "changeScope": "all",
    "compactUpdates": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/bizquest-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 = {
    "mode": "search",
    "locations": ["Florida"],
    "urls": [
        "https://www.bizquest.com/businesses-for-sale/",
        "https://www.bizquest.com/businesses-for-sale/page-2/",
        "https://www.bizquest.com/businesses-for-sale-in-florida-fl/",
        "https://www.bizquest.com/businesses-for-sale-in-florida-fl/page-2/",
        "https://www.bizquest.com/businesses-for-sale-in-miami-fl/",
        "https://www.bizquest.com/businesses-for-sale-in-miami-dade-county-fl/",
        "https://www.bizquest.com/food-and-beverage-businesses-for-sale/",
        "https://www.bizquest.com/food-and-beverage-businesses-for-sale-in-florida/",
        "https://www.bizquest.com/restaurants-for-sale/",
        "https://www.bizquest.com/restaurants-for-sale-in-florida/",
        "https://www.bizquest.com/businesses-for-sale-in-florida-fl/?q=%3Ff_srt%3Dprice-asc",
        "https://m.bizquest.com/businesses-for-sale-in-florida-fl/",
        "https://www.bizquest.com/business-broker/transworld-business-brokers/james-knox/BW29511/",
        "https://www.bizquest.com/florida-business-brokers/",
    ],
    "maxPages": 0,
    "maxListings": 20,
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "expireAfterMissedScans": 1,
    "changeScope": "all",
    "compactUpdates": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/bizquest-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 '{
  "mode": "search",
  "locations": [
    "Florida"
  ],
  "urls": [
    "https://www.bizquest.com/businesses-for-sale/",
    "https://www.bizquest.com/businesses-for-sale/page-2/",
    "https://www.bizquest.com/businesses-for-sale-in-florida-fl/",
    "https://www.bizquest.com/businesses-for-sale-in-florida-fl/page-2/",
    "https://www.bizquest.com/businesses-for-sale-in-miami-fl/",
    "https://www.bizquest.com/businesses-for-sale-in-miami-dade-county-fl/",
    "https://www.bizquest.com/food-and-beverage-businesses-for-sale/",
    "https://www.bizquest.com/food-and-beverage-businesses-for-sale-in-florida/",
    "https://www.bizquest.com/restaurants-for-sale/",
    "https://www.bizquest.com/restaurants-for-sale-in-florida/",
    "https://www.bizquest.com/businesses-for-sale-in-florida-fl/?q=%3Ff_srt%3Dprice-asc",
    "https://m.bizquest.com/businesses-for-sale-in-florida-fl/",
    "https://www.bizquest.com/business-broker/transworld-business-brokers/james-knox/BW29511/",
    "https://www.bizquest.com/florida-business-brokers/"
  ],
  "maxPages": 0,
  "maxListings": 20,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "expireAfterMissedScans": 1,
  "changeScope": "all",
  "compactUpdates": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/bizquest-scraper --silent --output-dataset

```

## MCP server setup

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