# Businesses For Sale — BizBuySell, BizQuest, BusinessesForSale (`shelvick/business-for-sale-aggregator`) Actor

Buy a business: aggregate small business for sale listings and M\&A listings from BizBuySell, BizQuest, DealStream and BusinessesForSale into one deduplicated business acquisition deal flow feed. Filter by sector, location, asking price, revenue; get price, revenue, cash flow, broker and source link.

- **URL**: https://apify.com/shelvick/business-for-sale-aggregator.md
- **Developed by:** [Scott Helvick](https://apify.com/shelvick) (community)
- **Categories:** Business, Automation, Lead generation
- **Stats:** 9 total users, 3 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $127.50 / 1,000 deal feed returneds

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

**Buying a business means tab-hopping across a dozen broker marketplaces**, each with its
own layout, currency, and price formatting. This Actor aggregates business-for-sale and
small-M\&A listings from multiple broker marketplaces into **one normalized, deduplicated
acquisition deal feed** — structured asking price, revenue, cash flow, sector, location,
and broker, plus a link back to each source listing — so acquirers, search funds, and
brokers screen acquisition deal flow in a single pass.

**One run returns listings like this:**

```json
{
  "title": "Profitable SaaS Business with Recurring Revenue",
  "askingPriceUsd": 1200000,
  "revenueUsd": 850000,
  "cashFlowUsd": 300000,
  "sector": "Software & Technology",
  "location": "California, United States",
  "source": "bizquest",
  "sourceUrl": "https://www.bizquest.com/business-for-sale/profitable-saas/abc123"
}
```

The input is prefilled to pull software and e-commerce deals under $5M across all four
marketplaces — the first run needs zero setup, so just start it.

### What this does

- Pulls listings from multiple broker marketplaces in one run — currently **DealStream,
  BizQuest, BusinessesForSale, and BizBuySell** — and merges them into a single structured dataset.
- Returns **structured financial facts** per listing: asking price (USD), annual
  revenue, cash flow / SDE, sector, location, broker, and the canonical source URL.
- **Filters across all sources at once**: by sector/industry, location, asking-price
  range, and minimum revenue — so you screen the whole field with one set of criteria.
- **Deduplicates** by source + listing id, returning a clean, agent-ready feed.
- **Facts-first, link-out:** each record carries the structured facts plus a short
  summary and a link to the full listing on its source — it points you to the deal, it
  doesn't rehost the seller's full write-up or photos.
- Built **agent-first**: one structured dataset record per listing, designed to be
  filtered and ranked by an LLM or a downstream pipeline.

### Why a normalized multi-source feed matters

The business-for-sale market is fragmented across many independent and franchise-broker
marketplaces, each siloed behind its own search UI. Single-site scrapers hand you one
marketplace in that marketplace's raw shape — you still do the cross-source merge,
currency normalization, and dedup by hand. The value here is the **layer above the
single-site scrape**: consistent fields across sources, USD-normalized prices, dedup,
and one filter pass — the part that's tedious and brittle to build yourself, and that no
single-site scraper provides.

### How it compares to single-site scrapers

| | Single-site scraper | Business For Sale Aggregator |
|---|---|---|
| Sources per run | one marketplace | multiple marketplaces |
| Output shape | each site's raw fields | one normalized schema |
| Price | as-displayed (mixed currencies) | USD-normalized (non-USD left explicit) |
| Cross-source dedup | none | yes |
| Filtering | per-site | unified across sources |
| Use | scrape one site | screen the whole field |

### Input

| Field | Type | Required | Description |
|---|---|---|---|
| `sources` | array of string | no | Which broker marketplaces to aggregate. Valid: `dealstream`, `bizquest`, `businessesforsale`, `bizbuysell`. Empty = all supported. |
| `sectors` | array of string | no | Industry/sector keywords to filter on (e.g. `software`, `restaurant`). Empty = all sectors. |
| `locations` | array of string | no | Location keywords (country/state/region/city). Empty = all locations. |
| `minAskingPriceUsd` | integer | no | Only listings at/above this asking price (USD). |
| `maxAskingPriceUsd` | integer | no | Only listings at/below this asking price (USD); excludes undisclosed-price listings. |
| `minRevenueUsd` | integer | no | Only listings reporting at/above this annual revenue (USD). |
| `maxListings` | integer | no | Cap on normalized listings returned (default 100, max 1000). |
| `enrichDetails` | boolean | no | Also open each returned listing's own page and add the extra financial facts published there. Off by default; at most the first 100 listings of a run are enriched. See below. |

### Output

Every dataset record includes `retry`, derived from the record-level `errorClass`: `null` for a result, `later` to retry after a short delay, `with_changes` to correct the input before retrying, or `never` to avoid repeating the same request.

One dataset record per normalized, deduplicated listing:

```json
{
  "listingId": "bizquest:abc123",
  "title": "Profitable SaaS Business with Recurring Revenue",
  "askingPriceUsd": 1200000,
  "revenueUsd": 850000,
  "cashFlowUsd": 300000,
  "sector": "Software & Technology",
  "location": "California, United States",
  "broker": "Acme Business Brokers",
  "source": "bizquest",
  "sourceUrl": "https://www.bizquest.com/business-for-sale/profitable-saas/abc123",
  "listedAt": "2026-05-30",
  "description": "Established SaaS with high gross margins and low churn...",
  "fetchedAt": "2026-06-17T08:00:00Z"
}
```

With `enrichDetails` on, each enriched record also carries the detail-page fields —
`contactName`, `contactPhone`, `listedBy`, `brokerage`, `brokerProfileUrl`,
`ebitdaUsd`, `inventoryUsd`, `ffeUsd`, `realEstate`, `yearEstablished`, `employees`,
`financingOffered` — and back-fills `revenueUsd` / `cashFlowUsd` where the search
result left them blank:

```json
{
  "listingId": "bizquest:abc123",
  "title": "Profitable SaaS Business with Recurring Revenue",
  "askingPriceUsd": 1200000,
  "revenueUsd": 850000,
  "cashFlowUsd": 300000,
  "ebitdaUsd": 275000,
  "inventoryUsd": 40000,
  "ffeUsd": 120000,
  "realEstate": "Leased",
  "yearEstablished": 1998,
  "employees": 12,
  "financingOffered": true,
  "contactName": "Alex Sample",
  "contactPhone": "555-010-1234",
  "listedBy": "broker",
  "brokerage": "Example Business Brokers",
  "sector": "Software & Technology",
  "location": "California, United States",
  "source": "bizquest",
  "sourceUrl": "https://www.bizquest.com/business-for-sale/profitable-saas/abc123",
  "fetchedAt": "2026-06-17T08:00:00Z"
}
```

Financial fields are `null` when the source doesn't disclose them. `askingPriceUsd` is
populated only when the source quotes USD — non-USD listings keep a `null` USD price (so
a foreign-currency figure is never silently mislabeled) until currency conversion ships.

### Optional detail enrichment

Set `enrichDetails` to `true` and the run opens each returned listing's own page and
adds the extra facts published there. **Off by default.**

| Field | Type | What it is |
|---|---|---|
| `contactName` | string | The name printed under "Listed By" on the listing |
| `contactPhone` | string | The phone printed on the listing, digits and dashes |
| `listedBy` | string | `broker` or `owner` — who published the listing |
| `brokerage` | string | Brokerage name — broker-listed businesses only |
| `brokerProfileUrl` | string | Link to the broker's profile — broker-listed businesses only |
| `cashFlowUsd` | number | Cash flow / SDE — back-filled when the search result didn't show it |
| `revenueUsd` | number | Gross revenue — back-filled when the search result didn't show it |
| `ebitdaUsd` | number | Reported EBITDA |
| `inventoryUsd` | number | Inventory value |
| `ffeUsd` | number | Furniture, fixtures and equipment value |
| `realEstate` | string | Real-estate arrangement (owned, leased, included in the sale) |
| `yearEstablished` | integer | Four-digit year the business was established |
| `employees` | integer | Headcount as reported |
| `financingOffered` | boolean | Whether seller financing is stated as offered |

- **At most the first 100 listings of a run are enriched.** Listings beyond that keep
  their search-result fields; the run summary reports how many were skipped.
- **Included in the per-run fee** — enrichment does not change what a run costs you.
  It does add runtime.
- **Every field is nullable.** A source that doesn't publish a fact leaves it `null`.
  A small share of detail pages may be unavailable on a given run — those listings are
  returned un-enriched, keeping their search-result fields, and the run still succeeds.
  The run summary reports how many detail pages were read and how many weren't.
- **The contact the listing publishes.** `contactName` and `contactPhone` carry the
  name and number the marketplace prints on the listing, and **`listedBy` tells you
  whose they are**: `broker` when the marketplace attributes the listing to a named
  brokerage, `owner` when the business is being sold directly by its owner. That
  distinction is worth filtering on — owner-listed deals mean no broker in the middle,
  while broker-listed ones also carry `brokerage` and `brokerProfileUrl` so you can see
  the firm's other listings. Both are `null` for sources that print no contact, and
  `listedBy` is `null` when no contact is printed. **Email addresses are never
  returned**, from any listing.
- **Facts only.** Enrichment never returns descriptions, marketing copy, or photos.
- **Which fields you actually get varies by marketplace.** Each source publishes a
  different subset, and a fact the source doesn't print is `null` rather than guessed.
  A figure that can't be read unambiguously — a range, a non-USD amount, an
  "on request" placeholder — is also left `null` rather than turned into a number
  that looks exact but isn't.

### Example

```json
{
  "sources": ["bizquest", "businessesforsale", "dealstream", "bizbuysell"],
  "maxListings": 100
}
```

Returns up to 100 normalized listings, merged and deduplicated across the four marketplaces.
Add `sectors`, `locations` or price filters to narrow it; `maxAskingPriceUsd` excludes listings that don't disclose a price.

### Calling from an AI agent

Discoverable via the **Apify MCP server** (`mcp.apify.com`) as
`shelvick/business-for-sale-aggregator`. Run it from the Apify Python SDK:

```python
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("shelvick/business-for-sale-aggregator").call(
    run_input={"sectors": ["software"], "maxListings": 50}
)
for listing in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(listing["title"], listing["askingPriceUsd"], listing["sourceUrl"])
```

Or the REST run-sync endpoint (`POST /v2/acts/shelvick~business-for-sale-aggregator/run-sync-get-dataset-items?token=…`)
with the same JSON input. Every output field is structured for direct filtering/ranking.

### Pricing

Pay-per-event, billed only on success: a single flat fee per run that returns at least one
listing, charged after the records are pushed. The fee is the same whether the run returns
one listing or hundreds, so broad multi-source searches stay predictable — and runs that
fail or return no listings are never charged. See the **Pricing** tab on this Store page for
the current per-run rate and any active subscriber discounts.

### Behavior

- **Per-source resilience:** if one marketplace fails to fetch, the run continues with
  the others and reports what it returned — a partial source outage never fails the run.
- **Latency:** typically under a minute for a single-sector run across all four sources;
  scales with the number of sectors requested.
- **Coverage:** v1 fetches the first search page per source per sector; very large result
  sets are sampled to `maxListings`. With `enrichDetails` on, up to 100 of the returned
  listings additionally have their own page read.
- **Deduplication:** exact source+id dedup within a run.
- **Telemetry:** to improve reliability and coverage, this Actor reports anonymous
  usage metrics and diagnostic events to the developer — run outcome counts,
  per-source health signals, and, only when something goes wrong, the relevant
  input fields. No account identifiers are collected, and telemetry never affects
  a run.

### FAQ

**Which marketplaces are covered?** DealStream, BizQuest, BusinessesForSale, and BizBuySell today.
More sources are planned.

**Are prices converted to USD?** USD-quoted listings are returned in USD; non-USD listings
return a `null` USD price (never a mislabeled figure) until currency conversion ships.

**Does it return full prospectuses?** No — it returns structured facts plus a link to
the source listing, where the full write-up lives. The published contact name and phone
are available via `enrichDetails`; email addresses never are.

**Can I get fuller financials per listing?** Turn on `enrichDetails` to add EBITDA,
inventory, FF\&E, real estate, year established, employees, and financing, and to
back-fill revenue and cash flow — for up to 100 listings per run, at no extra cost.

**Does it return contact details?** Yes, where the listing publishes them:
`enrichDetails` returns the name and phone printed on the listing, plus `listedBy` so
you know whether you're looking at a broker or the owner, and the brokerage name and
profile link for broker-listed businesses. Email addresses are never returned.

**Can I filter by revenue or cash flow?** You can filter by minimum revenue and by
asking-price range; every record also includes revenue and cash flow when disclosed for
your own downstream filtering.

### What this doesn't do

- No authentication / no gated or members-only listings — public search results only.
- No full descriptions or photos rehosted — facts + a source link only, detail
  enrichment included.
- No commercial real estate (LoopNet-style property listings) — this is operating
  businesses for sale.
- No email addresses, ever — published phone contacts only.
- No automated outreach or CRM sync.

Use a **single-site scraper** if you only need one marketplace in its raw shape and don't
need cross-source normalization. Use a **commercial-real-estate listings** tool for
property (not business) deals. Use a dedicated **company-financials** tool if you need
audited financials rather than broker-reported figures.

***

### Related Actors

| Actor | Use it when |
|---|---|
| [Secretary of State Business Search + UCC Liens](https://apify.com/shelvick/business-entity-lien-search) | you've found a business to buy — verify the legal entity and check for UCC liens |
| [Local Market Saturation & Competitor Density Analyzer](https://apify.com/shelvick/local-market-saturation) | you want to gauge how saturated the local market is before you commit |

# Actor input Schema

## `sources` (type: `array`):

Which broker marketplaces to aggregate. Valid values: bizquest, businessesforsale, dealstream, bizbuysell. Leave empty to use all supported sources. Validated at runtime; unknown values are rejected with a clear error. Returns facts plus a source link per listing (no rehosted descriptions or photos).

## `sectors` (type: `array`):

Optional industry/sector keywords to filter listings (e.g. software, ecommerce, manufacturing, restaurant). Matched case-insensitively against each listing's sector/category. Leave empty for all sectors.

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

Optional location keywords to filter listings (country, state, region, or city, e.g. 'United States', 'California', 'London'). Matched case-insensitively against each listing's location. Leave empty for all locations.

## `minAskingPriceUsd` (type: `integer`):

Only return listings with an asking price at or above this value (USD), e.g. 250000. Listings with no disclosed price are kept unless you also set a maximum. Leave empty for no lower bound.

## `maxAskingPriceUsd` (type: `integer`):

Only return listings with an asking price at or below this value (USD). Listings with no disclosed price are excluded when this is set. Leave empty for no upper bound.

## `minRevenueUsd` (type: `integer`):

Only return listings reporting annual revenue at or above this value (USD), e.g. 100000. Listings with no disclosed revenue are kept unless this is set. Leave empty for no revenue filter.

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

Maximum number of normalized, deduplicated listings to return across all sources. Caps cost and runtime. 1-1000.

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

Fetch each returned listing's own detail page and add the extra facts published there: cash flow / SDE, EBITDA, gross revenue, inventory, FF\&E, real estate, year established, employees, whether financing is offered, and the contact printed on the listing (name, phone, and whether it is listed by a broker or by the owner, plus the brokerage for broker-listed businesses). At most the first 100 listings of a run are enriched; any beyond that keep their search-result fields. Fields the source does not publish stay null. A small share of detail pages may be unavailable on a given run; those listings are returned un-enriched and the run still succeeds. Facts only — no descriptions, photos, or email addresses. Off by default; the per-run fee is the same either way. Adds runtime.

## Actor input object example

```json
{
  "sources": [
    "bizquest",
    "businessesforsale",
    "dealstream",
    "bizbuysell"
  ],
  "sectors": [],
  "locations": [],
  "maxListings": 100,
  "enrichDetails": false
}
```

# Actor output Schema

## `listings` (type: `string`):

Normalized, deduplicated business-for-sale listings for this run. When enrichDetails is on, the first 100 listings also carry the detail-page facts (EBITDA, inventory, FF\&E, real estate, year established, employees, financing offered) and the contact printed on the listing (contactName, contactPhone, listedBy), plus the brokerage and broker profile link where the listing is broker-published.

## `run_summary` (type: `string`):

Per-source fetched/parsed counts, deduped total, detail-enrichment counts (requested / succeeded / failed / over the cap / with a contact printed), and any source layout-break warnings. Check this to detect a source that silently returned zero listings.

# 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 = {
    "sources": [
        "bizquest",
        "businessesforsale",
        "dealstream",
        "bizbuysell"
    ],
    "sectors": [],
    "locations": [],
    "maxListings": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("shelvick/business-for-sale-aggregator").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 = {
    "sources": [
        "bizquest",
        "businessesforsale",
        "dealstream",
        "bizbuysell",
    ],
    "sectors": [],
    "locations": [],
    "maxListings": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("shelvick/business-for-sale-aggregator").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 '{
  "sources": [
    "bizquest",
    "businessesforsale",
    "dealstream",
    "bizbuysell"
  ],
  "sectors": [],
  "locations": [],
  "maxListings": 100
}' |
apify call shelvick/business-for-sale-aggregator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,shelvick/business-for-sale-aggregator"
        }
    }
}
```

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/ehtb0jeg1ftj7vfbZ/builds/8alAOkUL0q00SXHIz/openapi.json
