# Local Business Opportunity Finder (`skootle/local-business-opportunity-finder`) Actor

Turn business exports into ranked, evidence-backed service opportunities, clean no-signal results, and editable outreach drafts.

- **URL**: https://apify.com/skootle/local-business-opportunity-finder.md
- **Developed by:** [Skootle](https://apify.com/skootle) (community)
- **Categories:** Lead generation, SEO tools, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 verified business audits

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

## Local Business Opportunity Finder

![Local business opportunity research](https://v3b.fal.media/files/b/0aaf439a/jVEKhG91hw61pQn95DA6R_tjmTxiSZ.png)

### Use case

Find evidence-supported local-business opportunities from public business rows you already have. Choose `web_design`, `local_seo`, or `booking_automation`. The Actor produces sorted prospect records, bounded page evidence, evidence IDs, and editable drafts. It never sends outreach or uses an LLM.

### Buyer outcomes

- Turn an existing business export or Apify dataset into ranked service opportunities.
- Keep every finding tied to the exact public pages and retrieval time that support it.
- Separate verified opportunities, clean `no_signal` results, and unavailable `unknown` results instead of charging for silent empties.
- Get an agent-ready Markdown briefing plus structured dataset rows without providing an external AI key.

### Example

```json
{
  "mode": "imported_businesses",
  "businessType": "locksmith",
  "location": "Richmond, Virginia",
  "service": "local_seo",
  "maxBusinesses": 25,
  "importedBusinesses": [{ "businessName": "Example Locksmith", "websiteUrl": "https://example.com", "address": "Example address" }]
}
```

Use `importedDatasetId` instead of `importedBusinesses` when you have an Apify dataset the run token can read. It is paginated and only public-business fields are retained.

### Output

The dataset writes sorted prospect records first and the final summary last. `outputCounts` reports the rows actually emitted as `opportunity`, `no_signal`, and `unknown`. A prospect includes bounded website evidence and a priority score.

- `unknown` has no asserted website-review conclusion, offer, or outreach draft.
- `no_signal` has no offer or outreach draft.
- `web_design` can say only `listing has no website URL` when that field is absent. It does not claim the business has no website.
- `local_seo` identifies missing title, metadata, and `noindex` only per checked page. A signal is not a guaranteed defect.
- `booking_automation` treats a missing anchor as a weak, limited observation, never proof that no booking process exists.

The Actor retains generic public business-email patterns and public business-phone values only. It does not retain reviewer identities, profiles, images, personal-contact enrichment, or send contact.

### Charging

Each **Verified business audit** costs **$0.005** through the `successful-prospect-output` event: supported `opportunity` rows and clean `no_signal` rows after a successful public website inspection. Buyers pay for verified filtering as well as supported findings. `unknown`, source failures, and listing-only rows with no independently inspected website remain nonchargeable. Buyers cannot enable or disable charging through input. Before each chargeable audit is published, the SDK checks that one whole event remains within the runtime charge limit and then writes it through `Actor.pushData(row, 'successful-prospect-output')`; no separate precharge occurs. The final dataset summary is written last and reports the actual emitted and charged verified-audit counts. Local non-monetized runs remain usable.

### Maps dependency

`maps_discovery` calls `compass/crawler-google-places` with enrichment, images, and review-personal-data extraction disabled. It requires an Apify plan that permits the public Actor dependency. If that dependency is unavailable or not `SUCCEEDED`, the run fails clearly and does not return an empty successful result. The upstream Maps Actor may charge separately under its own pricing. Those charges are not included or measured here. Imported-business and imported-dataset modes do not require that upstream dependency.

### FAQ

**Does it contact businesses?** No. Drafts are editable and remain unsent.

**How much of a website is checked?** Up to three same-origin public HTML pages. Credentials, custom ports, private/reserved targets, redirects to unsafe targets, and overlarge responses are rejected. Production requests use DNS-pinned transport to prevent rebinding.

**Can I use a Maps dataset I already own?** Yes, provide `importedDatasetId` with a token authorized for that dataset.

### Support

Include the run ID, input mode, and the evidence IDs or URLs you want reviewed. Do not include credentials or personal-contact data.

### Development

The image uses Node 22.

```bash
npm ci
npm run typecheck
npm test
npm run build
npm run smoke
docker build -t local-business-opportunity-finder .
```

# Actor input Schema

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

Use imported public business rows or, when your plan permits it, the disclosed Maps dependency.

## `businessType` (type: `string`):

Describe the type of local business to research.

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

Name the market or locality associated with the imported businesses.

## `service` (type: `string`):

Choose the service used to evaluate public website evidence.

## `importedBusinesses` (type: `array`):

For imported_businesses mode. Public business fields only.

## `importedDatasetId` (type: `string`):

Optional caller-authorized Apify dataset ID. Its rows are paginated and reduced to public business fields.

## `maxBusinesses` (type: `integer`):

Positive finite limit. Maps mode forwards it to the upstream Actor.

## `maxPagesPerBusiness` (type: `integer`):

Bounded same-origin pages checked for evidence.

## `requestTimeoutSecs` (type: `integer`):

Maximum time allowed for each bounded public website request.

## Actor input object example

```json
{
  "mode": "imported_businesses",
  "businessType": "locksmith",
  "location": "Richmond, Virginia",
  "service": "local_seo",
  "importedBusinesses": [
    {
      "businessName": "Example Domain",
      "websiteUrl": "https://example.com"
    }
  ],
  "maxBusinesses": 1,
  "maxPagesPerBusiness": 1,
  "requestTimeoutSecs": 15
}
```

# Actor output Schema

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

Default dataset with a summary row and prospect rows.

## `agentBriefing` (type: `string`):

Short summary derived from the dataset summary.

# 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": "imported_businesses",
    "businessType": "example public website",
    "location": "Example market",
    "service": "local_seo",
    "importedBusinesses": [
        {
            "businessName": "Example Domain",
            "websiteUrl": "https://example.com"
        }
    ],
    "maxBusinesses": 1,
    "maxPagesPerBusiness": 1,
    "requestTimeoutSecs": 15
};

// Run the Actor and wait for it to finish
const run = await client.actor("skootle/local-business-opportunity-finder").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": "imported_businesses",
    "businessType": "example public website",
    "location": "Example market",
    "service": "local_seo",
    "importedBusinesses": [{
            "businessName": "Example Domain",
            "websiteUrl": "https://example.com",
        }],
    "maxBusinesses": 1,
    "maxPagesPerBusiness": 1,
    "requestTimeoutSecs": 15,
}

# Run the Actor and wait for it to finish
run = client.actor("skootle/local-business-opportunity-finder").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": "imported_businesses",
  "businessType": "example public website",
  "location": "Example market",
  "service": "local_seo",
  "importedBusinesses": [
    {
      "businessName": "Example Domain",
      "websiteUrl": "https://example.com"
    }
  ],
  "maxBusinesses": 1,
  "maxPagesPerBusiness": 1,
  "requestTimeoutSecs": 15
}' |
apify call skootle/local-business-opportunity-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,skootle/local-business-opportunity-finder"
        }
    }
}
```

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/87lhcRh3YHtYOJ9F5/builds/VkohyJu8dhpHu43AR/openapi.json
