# Business URL Discovery: B2B Lead Generation (`mechanical_spirit/business-url-discovery`) Actor

Turn search queries into verified company domains. Uses AI to filter noise and returns clean B2B leads at $0.005/result.

- **URL**: https://apify.com/mechanical\_spirit/business-url-discovery.md
- **Developed by:** [mechanical\_spirit](https://apify.com/mechanical_spirit) (community)
- **Categories:** AI, Lead generation, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, NaN bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.40 / 1,000 verified business url delivereds

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

<!-- WRITING RULES: No em dashes. No en dashes. No AI buzzwords.
     Plain English only. Active voice. If you edit this file, run:
     bash scripts/check-no-emdash.sh
     to confirm zero dash characters before committing. -->

## Business URL Discovery: B2B Lead Generation from Search

#### Find verified company domains at scale. Pay only for results.

**Business URL Discovery** discovers verified business websites from search queries across multiple sources. Results are deduplicated and AI-validated before delivery.

Two input modes:

- **City Batch** (recommended for most users): pick a profession and a region and the actor builds and runs the full city query list automatically.
- **Manual**: write your own queries and run them as a batch.

Pay $0.005 per verified domain. Nothing for failures, duplicates, or Maps-only listings.

---

### City Batch Mode

City batch mode is the fastest path to a full prospect list for a trade or service business. You specify a profession and a region. The actor generates one search query per city (ranked by population), runs all of them, and returns a single deduplicated dataset.

**Example: plumbers across Texas**

```json
{
  "inputMode": "cityBatch",
  "profession": "plumber",
  "regionCode": "TX",
  "topCities": 10,
  "maxResultsPerCity": 20
}
````

The actor generates and runs these 10 queries automatically:

```
plumber in Houston
plumber in San Antonio
plumber in Dallas
plumber in Austin
plumber in Fort Worth
plumber in El Paso
plumber in Arlington
plumber in Corpus Christi
plumber in Plano
plumber in Laredo
```

**Expected output:** Approximately 220 verified business domains (20 to 25 per city).
**Expected cost:** ~$1.13 (220 domains x $0.005 + $0.025 startup fee).

Scale estimates for cityBatch mode:

| Cities | Expected domains | Expected cost |
|---|---|---|
| 5 (default) | ~110 | ~$0.58 |
| 10 | ~220 | ~$1.13 |
| 30 | ~650 | ~$3.28 |

Manual mode delivers approximately 18 to 22 verified business websites per query.

No query writing. No city list to maintain. One input, one dataset.

#### Regional coverage

- **All 50 US states.** Cities ranked by population so the largest markets come first.
- **All 27 EU member states** (Austria, Belgium, Bulgaria, Croatia, Cyprus, Czech Republic, Denmark, Estonia, Finland, France, Germany, Greece, Hungary, Ireland, Italy, Latvia, Lithuania, Luxembourg, Malta, Netherlands, Poland, Portugal, Romania, Slovakia, Slovenia, Spain, Sweden).
- **Additional European markets:** United Kingdom, Norway, Switzerland, Iceland.
- **All 13 Canadian provinces and territories.**
- **Asia-Pacific and Americas:** Australia, New Zealand, Brazil, Mexico.

#### Language is set automatically

In city batch mode, the search language for each region is derived automatically. Germany (`DE`) uses German. France (`FR`) uses French. Quebec (`CA-QC`) uses French. Brazil (`BR`) uses Portuguese. Mexico (`MX`) uses Spanish. All US states, remaining Canadian provinces, UK, Ireland, Australia, and New Zealand use English. No manual language configuration needed.

#### Scaling city batch

| Region | topCities | Expected domains | Estimated cost |
|---|---|---|---|
| One US state, 5 cities | 5 | 75 to 100 | $0.40 to $0.53 |
| One US state, 10 cities | 10 | 150 to 200 | $0.78 to $1.03 |
| One US state, 25 cities | 25 | 350 to 500 | $1.78 to $2.53 |
| EU country, 10 cities | 10 | 100 to 180 | $0.53 to $0.93 |

***

### Who Uses This

**Sales reps and appointment setters**: build targeted prospect lists by industry and city. No Apollo subscription. No minimum spend. Export to CSV and import to your CRM in one run.

**Marketing agencies and lead gen freelancers**: use city batch mode to cover an entire state in one job. Run separate jobs for each client vertical. One dataset per job, ready to work.

**AI agent developers and automation builders**: structured JSON output with consistent field names on every run. Feed directly into enrichment actors, n8n workflows, Make scenarios, or any tool that calls the Apify REST API.

***

### Quick Start

**City batch mode (recommended):**

1. Set `inputMode` to `"cityBatch"`
2. Enter a `profession` (e.g. `"electrician"`) and a `regionCode` (e.g. `"TX"`)
3. Set `topCities` (default: 5) and `maxResultsPerCity` (default: 20)
4. Click **Run**

**Manual query mode:**

1. Add professions to the **Profession / Trade** list (e.g. Plumber, Electrician)
2. Add cities to the **City** list (e.g. Austin, Dallas, Houston)
3. Select the target **Country** (language is auto-derived from the country)
4. Click **Run**

Results appear in the dataset as they are discovered. Export as JSON or CSV, or pipe directly into Company Intelligence Profiler (coming soon). Each result is AI-validated before delivery.

***

### How Queries Are Built (Manual Mode)

The actor builds every possible combination of professions and cities. This is a cartesian product, not a positional pair.

**Example:**

Professions: `Plumber`, `Electrician`
Cities: `London`, `Berlin`

Produces 4 queries:

- Plumber in London
- Plumber in Berlin
- Electrician in London
- Electrician in Berlin

**Query math:**

| Professions | Cities | Queries | Max domains (at 25/query) |
|---|---|---|---|
| 1 | 3 | 3 | 75 |
| 2 | 3 | 6 | 150 |
| 3 | 5 | 15 | 375 |
| 5 | 10 | 50 | 1,250 |

Duplicates across queries are removed automatically and never charged twice. A domain found via multiple queries appears once in the dataset.

**All results are processed in one run.** Export as JSON or CSV, or pipe directly into Company Intelligence Profiler (coming soon).

***

### What You Get

Each record in the output dataset:

| Field | Type | Description |
|---|---|---|
| `domain` | string or null | Clean domain: `"company.com"`. Null for Maps-only listings with no website. |
| `companyName` | string | Company name extracted from search result title or AI |
| `snippet` | string | Search snippet or business address |
| `query` | string | The search query that found this result |
| `pageTitle` | string | Raw page title from the search result |
| `pageDescription` | string | Meta description or snippet text as returned by the search engine |
| `rank` | integer or null | Position in results (1 = top). Null for local listings. |
| `confidence` | number | AI validation score (0 to 1). Higher = more confident it is a real business. |
| `has_website` | boolean | `true` if a domain was found. `false` for Maps-only listings. |
| `discoveredAt` | string | ISO 8601 timestamp |

**Sample output:**

```json
{
  "domain": "riversideplumbing.com",
  "companyName": "Riverside Plumbing Co",
  "snippet": "Austin's trusted plumber since 1987. 24/7 emergency service.",
  "query": "plumbers in Austin Texas",
  "rank": 3,
  "confidence": 0.92,
  "has_website": true,
  "discoveredAt": "2026-03-27T10:14:22.000Z"
}
```

***

### Using This Actor in Automations and AI Agents

Every run returns structured JSON with the same field names regardless of which sources contributed results. `domain`, `companyName`, `confidence`, `has_website` are present and consistently typed on every record. Your pipeline never needs to handle missing fields.

To run this actor via API or inside an automation workflow, you need your own Apify API token. Find it at console.apify.com under Account Settings, Integrations. Each API call must include this token in the Authorization header. Do not share your token or commit it to version control.

**Call via the Apify REST API:**

```bash
## Find your Apify API token at: https://console.apify.com/account/integrations
curl -X POST \
  "https://api.apify.com/v2/acts/your-username~business-url-discovery/runs" \
  -H "Authorization: Bearer <your-apify-api-token>" \
  -H "Content-Type: application/json" \
  -d '{"inputMode": "cityBatch", "profession": "plumber", "regionCode": "TX", "topCities": 5}'
```

The response includes a `defaultDatasetId`. Poll the run until it finishes, then fetch results:

```bash
curl "https://api.apify.com/v2/datasets/{defaultDatasetId}/items" \
  -H "Authorization: Bearer <your-apify-api-token>"
```

**City batch via API:**

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/your-username~business-url-discovery/runs" \
  -H "Authorization: Bearer <your-apify-api-token>" \
  -H "Content-Type: application/json" \
  -d '{"inputMode": "cityBatch", "profession": "electrician", "regionCode": "TX", "topCities": 10}'
```

**Pipe into Company Intelligence Profiler (coming soon):** The `domain` field feeds directly into Company Intelligence Profiler with no transformation. Filter `has_website === true` first to exclude results with no website, then pass each `domain` as input.

**Compatible with:** n8n, Make (Integromat), Zapier, LangChain, any custom tool that calls the Apify REST API, and all Apify client libraries (JavaScript, Python).

***

### Use Cases

**Sales prospecting**
Build targeted lead lists without a ZoomInfo or Apollo subscription. Use city batch mode to cover a full state in minutes. Export to CSV and import to your CRM.

**Agency lead generation**
Run city batch for 10 cities in one job. Cover multiple states or countries with separate jobs. One dataset per job, deduplicated, ready for cold outreach or further enrichment.

**Market research**
Map the competitive landscape in a niche. Identify who ranks for your target keywords across multiple markets.

**CRM enrichment**
Have company names but no website? Run each name as a manual query and resolve the domain.

**Pipeline automation**
Feed this actor's output directly into **Company Intelligence Profiler** to turn every discovered domain into a full B2B profile: firmographics, tech stack, key people, contact info, and ICP scoring summary. Total pipeline cost: $0.055 per company.

**International prospecting**
City batch mode supports US, EU, UK, Canada, Australia, New Zealand, Brazil, and Mexico with automatic language handling. For manual queries, set `manualCountry` and `manualLocale` to match your target market.

***

### Supported Industries

The AI classifier and blocklist have been tested across these industry types:

**Home services:** plumbing, HVAC, electrical, roofing, painting, landscaping, pest control. Low directory noise; most results are direct business websites.

**Professional services:** accounting, law, dentistry, optometry, chiropractic, physical therapy. Higher directory noise (Yelp, Healthgrades, Avvo). The blocklist covers the major aggregators for these verticals.

**Retail and local:** florists, bakeries, pet shops, hardware stores, pharmacies, auto repair. Results vary by market density; smaller cities return more direct business sites than major metros.

**B2B and SaaS:** software companies, IT services, marketing agencies, consulting firms. Low local listing coverage (B2B businesses rarely appear in local directories). Web search results are the primary source.

**Restaurants and hospitality:** higher aggregator leakage risk. DoorDash, Grubhub, Yelp, TripAdvisor, and OpenTable are all blocked, but delivery-only ghost kitchens often have no direct website. Expect a higher `domain: null` rate for these queries.

**Real estate:** very high portal leakage risk. Zillow, Realtor.com, Trulia, and similar are blocked but local broker sites vary widely in structure. Use `minimumAiConfidence: 0.7` or higher for real estate queries.

**Not recommended for:** individual freelancers, contractors on Fiverr/Upwork (they are individuals, not businesses with websites), vacation rentals (Airbnb/VRBO dominate and are blocked), or industries where most operators have no web presence.

***

### Part of a Two-Actor B2B Pipeline

This actor is Step 1. **Company Intelligence Profiler** (Step 2, coming soon to the Apify Store) completes the pipeline.

Feed the domains discovered here into Company Intelligence Profiler to get:

- Firmographics (industry, size, HQ location, business model)
- Technology stack detection
- Key people (name + title)
- Contact information (email, phone)
- Growth signals (open roles, funding rounds, new products)
- ICP scoring summary for your sales team

**Total pipeline cost: $0.055 per domain** ($0.005 discovery + $0.05 enrichment). A complete, enriched B2B profile for under 6 cents, no subscription, no minimums.

***

### Automate with n8n

The Apify n8n node handles API token injection automatically once you connect your Apify account in n8n credentials. You do not need to add the token manually to any step below.

```
Step 1 - Run Business URL Discovery
  Node: Apify > Run Actor
  Input: { "inputMode": "cityBatch", "profession": "plumber", "regionCode": "TX", "topCities": 10 }

Step 2 - Get Dataset Items
  Dataset ID: {{ $json.defaultDatasetId }}

Step 3 - Filter (domains only)
  If: {{ $json.has_website }} is true

Step 4 - Split In Batches (size: 1)

Step 5 - Enrich each domain (requires Company Intelligence Profiler, coming soon)
  Node: Apify > Run Actor (Company Intelligence Profiler)
  Input: { "domains": ["{{ $json.domain }}"] }
```

The `domain` field feeds into Company Intelligence Profiler with no mapping or transformation.

***

### Input Parameters

#### City Batch Mode

| Parameter | Type | Default | Description |
|---|---|---|---|
| `inputMode` | string | `"manual"` | Set to `"cityBatch"` to auto-generate queries by region |
| `profession` | string | Required | Business type to search for. E.g. `"plumber"`, `"electrician"`, `"family dentist"` |
| `regionCode` | string | Required | US state, EU country, Canadian province, or country code for AU/NZ/BR/MX. E.g. `"TX"`, `"DE"`, `"CA-ON"`, `"AU"` |
| `topCities` | integer | 5 | Number of top cities to search in the selected country. Default 5 is recommended for first runs. Increase to 10-20 for production campaigns. (1 to 20) |
| `maxResultsPerCity` | integer | 25 | Max charged results per city. AI filters candidates; you pay only for validated results up to this cap. Typical yield is 20 to 25 per city. (5 to 30) |

Language is derived automatically from the region in city batch mode. Germany (`DE`) uses German. France (`FR`) uses French. Brazil (`BR`) uses Portuguese. Mexico (`MX`) uses Spanish. All US states, Canadian provinces, UK, Australia, and New Zealand use English.

#### Manual Query Mode

| Parameter | Type | Default | Description |
|---|---|---|---|
| `inputMode` | string | `"manual"` | `"manual"` for direct queries. `"cityBatch"` to auto-generate by region. |
| `queryProfessions` | string\[] | Required | List of professions or trades. E.g. `["Plumber", "Electrician"]`. Combined with every city in `queryCities`. Write in your target language (e.g. `"Klempner"` for Germany). |
| `queryCities` | string\[] | Required | List of cities. E.g. `["Austin", "Dallas"]`. Combined with every profession in `queryProfessions`. |
| `manualCountry` | string | `"us"` | ISO country code for proxy routing and TLD filtering. Must match your target cities. E.g. `"de"`, `"dk"`, `"gb"`. Language is auto-derived from this field. |
| `manualLocale` | string | `"en"` | Language code override. Leave at default unless the auto-derived language for your country is wrong. |
| `maxResultsPerManualQuery` | integer | 20 | Max charged results per query. AI filters candidates; you pay only for validated results up to this cap. Typical yield is 15 to 22 per query. (5 to 25). |

#### Advanced Settings

| Parameter | Type | Default | Description |
|---|---|---|---|
| `minimumAiConfidence` | number | 0.6 | Minimum AI confidence to include a result (0.4 to 0.9). Lower = more results, more noise. |

All sources are always active.

**Example: German market (manual mode):**

```json
{
  "inputMode": "manual",
  "queryProfessions": ["Klempner", "B2B SaaS Unternehmen"],
  "queryCities": ["Berlin", "München", "Hamburg"],
  "manualCountry": "de"
}
```

This runs 6 queries (2 professions x 3 cities) with German-language search and German proxy routing. Write professions in the target language. `manualCountry` sets proxy routing and TLD filtering; language is derived automatically.

***

### Geographic Coverage

**United States:** Primary market. All sources tested at scale. Works correctly for metro-branded businesses and regional operators across the US.

**United Kingdom, Canada, Australia:** Fully supported. Set `manualCountry` to `gb`, `ca`, or `au`. UK and Australian business directories are filtered automatically.

**Germany, Austria, Switzerland (DACH):** Fully supported. In city batch mode, select the region and language is set automatically. For manual queries, set `manualCountry` to `de`, `at`, or `ch`. German business directories are filtered automatically.

**France:** Fully supported. City batch mode sets French automatically for French regions. For manual queries, set `manualCountry` to `fr`. French business directories are filtered automatically.

**Benelux (Netherlands, Belgium):** Fully supported. City batch mode sets Dutch automatically for Netherlands and Belgium. For manual queries, set `manualCountry` to `nl` or `be`. Dutch and Belgian business directories are filtered automatically.

**Nordics (Sweden, Norway, Denmark, Finland):** Fully supported. City batch mode sets the correct Nordic language for each country. Nordic business directories are filtered automatically.

**Iberia (Spain, Portugal):** Fully supported. City batch mode sets Spanish or Portuguese automatically. Spanish and Portuguese business directories are filtered automatically.

**Italy:** Fully supported. City batch mode sets Italian automatically. Italian business directories are filtered automatically.

**Poland, Czech Republic, Hungary, Romania:** Supported. City batch mode sets the correct local language. Local business directories are filtered automatically. Works correctly for metro-branded businesses in major cities.

**Non-Latin script markets (Arabic, Chinese, Japanese, Korean, Cyrillic):** Not recommended. The search mechanics work but the AI classifier and blocklist have not been tested on these scripts. Results will be inconsistent.

**How geo-targeting works:**

- In city batch mode, the language for each country is set automatically based on the selected region. No manual configuration is needed.
- For manual queries, set `manualCountry` (e.g. `de`, `gb`, `fr`) to tell each search engine which country's results to show. Language is then derived automatically from the country code.
- `manualCountry` is available in the Apify UI under Manual Query Mode and can also be passed via JSON input when calling via the Apify API.

***

### Pricing

**Pay per result. Nothing for failures, duplicates, or Maps-only listings.**

**Quick estimate:** A typical 10-city city batch run returns 150 to 200 verified domains and costs $0.78 to $1.03 total (domains + $0.025 start fee).

#### Standard Pricing

| Event | Price | When charged |
|---|---|---|
| Actor start | $0.025 | Once per run, before any queries run |
| Per verified domain | $0.005 | Per unique business URL delivered |
| Maps-only result (no website) | Free | Captured and included, not charged |
| Duplicates across queries | Free | Deduplicated automatically |

#### Subscription Tier Discounts

Apify subscription plans get discounts on both events:

| Tier | Domain price | Start fee | Domain discount |
|---|---|---|---|
| Free | $0.0050 | $0.025 | None |
| Bronze | $0.0048 | $0.020 | 4% |
| Silver | $0.0046 | $0.0175 | 8% |
| Gold | $0.0044 | $0.0125 | 12% |

#### Spend Limit

Apify's built-in spend limit (`maxTotalChargeUsd`) controls the maximum charges per run. The default is $25.00. You can adjust it in the run configuration before starting a run, or pass it via the API. The run stops automatically and saves partial results when the limit is reached.

#### Volume Estimates

| Results | Domain cost (free tier) | Total (incl. start) |
|---|---|---|
| 100 | $0.50 | $0.53 |
| 500 | $2.50 | $2.53 |
| 1,000 | $5.00 | $5.03 |
| 4,995 | $24.98 | $25.00 (default limit reached) |

Note: Apify Proxy bandwidth adds approximately $0.003 to $0.010 per query to your Apify platform bill, separate from the per-domain charge above.

**How this compares:**

| | This Actor | Apollo | ZoomInfo | Manual Research |
|---|---|---|---|---|
| Price per domain | **$0.005** | ~$0.10 to $0.40 | ~$0.50+ | ~$5 to $15 (time) |
| Subscription required | **No** | Yes ($49+/mo) | Yes (enterprise) | No |
| Local businesses (Maps) | **Yes** | No | No | Manual |
| International queries | **Yes** | English-focused | English-focused | Manual |
| Pay only for results | **Yes** | No | No | N/A |
| City batch automation | **Yes** | No | No | Manual |

1,000 domains costs $5.03 here. The same list costs $100 to $400 on Apollo.

***

### Tips for Best Results

- Use city batch mode when you want full coverage of a profession across a state or country
- In manual mode, be specific with locations: `"plumbers in Austin Texas"` beats `"plumbers"`
- Combine industry + location for better coverage; avoid single-word queries
- All sources run automatically; deduplication is built in
- Local service businesses (plumbers, HVAC, roofers) often have a local listing but no website; those are captured and not charged
- Set `minimumAiConfidence: 0.7` for higher precision; `0.5` for maximum volume
- 20 to 25 results per city is the typical range; cityBatch mode delivers this consistently
- Results found in multiple sources get a confidence boost; prioritize high-confidence results for outreach

***

### Proxy Requirement

**Proxies are required.** Datacenter IPs are blocked by major search engines. This actor uses Apify proxies for all sources, configured automatically.

**No configuration needed.** You do not need to supply proxy credentials or toggle any proxy settings.

**Bandwidth cost:** Apify Proxy adds approximately $0.003 to $0.010 per query to your Apify platform bill. This is separate from the per-domain charge.

**Free plan note:** Proxy access requires a paid Apify plan. On the free tier results may be limited due to CAPTCHA blocks.

***

### Running Large Query Batches

**Default actor timeout: 3600 seconds (60 minutes).** This is already configured in the actor settings; you do not need to change it.

**Typical run durations:**

| Queries | Typical duration |
|---|---|
| 1 to 5 queries | 3 to 12 minutes |
| 10 to 15 queries | 20 to 35 minutes |
| 20 to 30 queries | 40 to 60 minutes |

These estimates apply to both city batch and manual modes. City batch with 10 cities is equivalent to 10 manual queries.

**If running via the Apify CLI with the `-t` flag**, set the timeout to match or exceed the actor's own timeout:

```bash
apify run -t 3600
```

The default CLI timeout of 1800 seconds (30 minutes) may cut off multi-query runs prematurely. The run itself will complete correctly on the Apify platform; the CLI just stops waiting for it. Fetch results from the dataset after the run finishes.

**On the Apify platform**, timeouts are managed by the platform directly. The actor's `defaultRunOptions.timeoutSecs: 3600` applies automatically on every run; no CLI flag needed.

***

### Limitations

- Results with no company website return `domain: null`. These are included free and not charged. Use `has_website === true` to filter before piping to downstream actors.
- Results are capped at 25 per query in manual mode (30 per city in city batch mode). For broader coverage, increase `topCities` or add more queries.
- Search engine HTML structure changes occasionally. When a source degrades to zero results, automated alerts fire and the issue is fixed in the next build update.
- This actor discovers domain URLs. It does not scrape the websites themselves. For firmographics, tech stack, and contact data, use Company Intelligence Profiler (coming soon) as Step 2.

***

### Categories

Lead Generation · AI · Agents

### Tags

b2b · leads · google-maps · url-discovery · business-finder · domains · lead-generation · prospecting · n8n · sales · crm · city-batch

# Actor input Schema

## `inputMode` (type: `string`):

Manual Query Mode: enter profession+city rows directly. City Batch Mode: enter a profession and region — the actor generates one query per major city automatically, ranked by population.

## `queryProfessions` (type: `array`):

One profession or trade per row. Every profession is combined with every city below — 2 professions x 3 cities = 6 queries. Each query can return up to Max Results Per Query domains. Write professions in the language of your target country (e.g. 'Klempner' for Germany, 'Loodgieter' for Netherlands). Set the Country field to match.

## `queryCities` (type: `array`):

One city per row. Every city is combined with every profession above — 3 professions x 4 cities = 12 queries. Check your query count before running large batches. Each query charges up to Max Results Per Query domains.

## `manualCountry` (type: `string`):

Country for proxy routing and TLD filtering. Must match the cities in your queries. Queries for different countries must run separately.

## `manualLocale` (type: `string`):

Language for search results. Defaults to English. Auto-derived from the Country field in most cases. Override only if the auto-derived language is wrong for your target.

## `maxResultsPerManualQuery` (type: `integer`):

Maximum charged results per query. You are only charged for results that pass AI validation, up to this cap. Typical yield is up to 20 verified business websites per query.

## `profession` (type: `string`):

The business type to search for. Be specific: plumber not services, family dentist not dentist. Used to generate phrases like plumber in Houston Texas. Write in the language of your target region (e.g. 'Klempner' for Germany). The Region field must match.

## `regionCode` (type: `string`):

The US state, EU country, or Canadian province to search. Cities are ranked by population so the most relevant markets come first.

## `topCities` (type: `integer`):

Number of top cities to search in the selected country. Default 5 is recommended for first runs. Increase to 10-20 for production lead generation campaigns.

## `maxResultsPerCity` (type: `integer`):

Maximum charged results per city. The actor scrapes more candidates than this and filters with AI. You are only charged for results that pass validation, up to this cap. Typical yield is up to 25 verified business websites per city. Max 30.

## `minimumAiConfidence` (type: `number`):

Domains scoring below this threshold are excluded and not charged. 0.6 is recommended. 0.8 or above gives higher precision with fewer results. 0.4 gives maximum coverage with occasional noise.

## Actor input object example

```json
{
  "inputMode": "manual",
  "queryProfessions": [
    "Electrician"
  ],
  "queryCities": [
    "Austin, TX"
  ],
  "manualCountry": "us",
  "manualLocale": "en",
  "maxResultsPerManualQuery": 20,
  "topCities": 5,
  "maxResultsPerCity": 25,
  "minimumAiConfidence": 0.6
}
```

# Actor output Schema

## `results` (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 = {
    "queryProfessions": [
        "Electrician"
    ],
    "queryCities": [
        "Austin, TX"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("mechanical_spirit/business-url-discovery").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 = {
    "queryProfessions": ["Electrician"],
    "queryCities": ["Austin, TX"],
}

# Run the Actor and wait for it to finish
run = client.actor("mechanical_spirit/business-url-discovery").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "queryProfessions": [
    "Electrician"
  ],
  "queryCities": [
    "Austin, TX"
  ]
}' |
apify call mechanical_spirit/business-url-discovery --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=mechanical_spirit/business-url-discovery",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Business URL Discovery: B2B Lead Generation",
        "description": "Turn search queries into verified company domains. Uses AI to filter noise and returns clean B2B leads at $0.005/result.",
        "version": "1.0",
        "x-build-id": "160aBAhTDQQXGhP3B"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/mechanical_spirit~business-url-discovery/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-mechanical_spirit-business-url-discovery",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/mechanical_spirit~business-url-discovery/runs": {
            "post": {
                "operationId": "runs-sync-mechanical_spirit-business-url-discovery",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/mechanical_spirit~business-url-discovery/run-sync": {
            "post": {
                "operationId": "run-sync-mechanical_spirit-business-url-discovery",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "inputMode": {
                        "title": "Input mode",
                        "enum": [
                            "manual",
                            "cityBatch"
                        ],
                        "type": "string",
                        "description": "Manual Query Mode: enter profession+city rows directly. City Batch Mode: enter a profession and region — the actor generates one query per major city automatically, ranked by population.",
                        "default": "manual"
                    },
                    "queryProfessions": {
                        "title": "Profession / Trade",
                        "type": "array",
                        "description": "One profession or trade per row. Every profession is combined with every city below — 2 professions x 3 cities = 6 queries. Each query can return up to Max Results Per Query domains. Write professions in the language of your target country (e.g. 'Klempner' for Germany, 'Loodgieter' for Netherlands). Set the Country field to match.",
                        "items": {
                            "type": "string"
                        },
                        "default": []
                    },
                    "queryCities": {
                        "title": "City",
                        "type": "array",
                        "description": "One city per row. Every city is combined with every profession above — 3 professions x 4 cities = 12 queries. Check your query count before running large batches. Each query charges up to Max Results Per Query domains.",
                        "items": {
                            "type": "string"
                        },
                        "default": []
                    },
                    "manualCountry": {
                        "title": "Country",
                        "enum": [
                            "us",
                            "gb",
                            "ca",
                            "au",
                            "nz",
                            "ie",
                            "at",
                            "be",
                            "ch",
                            "de",
                            "dk",
                            "es",
                            "fi",
                            "fr",
                            "is",
                            "lu",
                            "mt",
                            "nl",
                            "no",
                            "pt",
                            "se",
                            "bg",
                            "cy",
                            "cz",
                            "ee",
                            "gr",
                            "hr",
                            "hu",
                            "lv",
                            "lt",
                            "pl",
                            "ro",
                            "si",
                            "sk",
                            "it",
                            "in",
                            "jp",
                            "kr",
                            "sg",
                            "br",
                            "mx",
                            "za"
                        ],
                        "type": "string",
                        "description": "Country for proxy routing and TLD filtering. Must match the cities in your queries. Queries for different countries must run separately.",
                        "default": "us"
                    },
                    "manualLocale": {
                        "title": "Language",
                        "enum": [
                            "en",
                            "da",
                            "de",
                            "fr",
                            "nl",
                            "es",
                            "it",
                            "sv",
                            "nb",
                            "fi",
                            "pt",
                            "pl",
                            "cs",
                            "hu",
                            "ro",
                            "el",
                            "bg",
                            "hr",
                            "et",
                            "lv",
                            "lt",
                            "sk",
                            "sl",
                            "ja",
                            "ko"
                        ],
                        "type": "string",
                        "description": "Language for search results. Defaults to English. Auto-derived from the Country field in most cases. Override only if the auto-derived language is wrong for your target.",
                        "default": "en"
                    },
                    "maxResultsPerManualQuery": {
                        "title": "Max Results Per Query",
                        "minimum": 5,
                        "maximum": 25,
                        "type": "integer",
                        "description": "Maximum charged results per query. You are only charged for results that pass AI validation, up to this cap. Typical yield is up to 20 verified business websites per query.",
                        "default": 20
                    },
                    "profession": {
                        "title": "Trade or profession",
                        "type": "string",
                        "description": "The business type to search for. Be specific: plumber not services, family dentist not dentist. Used to generate phrases like plumber in Houston Texas. Write in the language of your target region (e.g. 'Klempner' for Germany). The Region field must match."
                    },
                    "regionCode": {
                        "title": "Region",
                        "enum": [
                            "AL",
                            "AK",
                            "AZ",
                            "AR",
                            "CA",
                            "CO",
                            "CT",
                            "US-DE",
                            "FL",
                            "GA",
                            "HI",
                            "ID",
                            "IL",
                            "IN",
                            "IA",
                            "KS",
                            "KY",
                            "LA",
                            "ME",
                            "MD",
                            "MA",
                            "MI",
                            "MN",
                            "MS",
                            "MO",
                            "US-MT",
                            "NE",
                            "NV",
                            "NH",
                            "NJ",
                            "NM",
                            "NY",
                            "NC",
                            "ND",
                            "OH",
                            "OK",
                            "OR",
                            "PA",
                            "RI",
                            "SC",
                            "SD",
                            "TN",
                            "TX",
                            "UT",
                            "VT",
                            "VA",
                            "WA",
                            "WV",
                            "WI",
                            "WY",
                            "AT",
                            "BE",
                            "BG",
                            "HR",
                            "CY",
                            "CZ",
                            "DK",
                            "EE",
                            "FI",
                            "FR",
                            "DE",
                            "GR",
                            "HU",
                            "IE",
                            "IT",
                            "LV",
                            "LT",
                            "LU",
                            "EU-MT",
                            "NL",
                            "PL",
                            "PT",
                            "RO",
                            "SK",
                            "SI",
                            "ES",
                            "SE",
                            "GB",
                            "NO",
                            "CH",
                            "IS",
                            "CA-AB",
                            "CA-BC",
                            "CA-MB",
                            "CA-NB",
                            "CA-NL",
                            "CA-NT",
                            "CA-NS",
                            "CA-NU",
                            "CA-ON",
                            "CA-PE",
                            "CA-QC",
                            "CA-SK",
                            "CA-YT",
                            "AU",
                            "NZ",
                            "BR",
                            "MX"
                        ],
                        "type": "string",
                        "description": "The US state, EU country, or Canadian province to search. Cities are ranked by population so the most relevant markets come first."
                    },
                    "topCities": {
                        "title": "Number of cities",
                        "minimum": 1,
                        "maximum": 20,
                        "type": "integer",
                        "description": "Number of top cities to search in the selected country. Default 5 is recommended for first runs. Increase to 10-20 for production lead generation campaigns.",
                        "default": 5
                    },
                    "maxResultsPerCity": {
                        "title": "Max results per city",
                        "minimum": 5,
                        "maximum": 30,
                        "type": "integer",
                        "description": "Maximum charged results per city. The actor scrapes more candidates than this and filters with AI. You are only charged for results that pass validation, up to this cap. Typical yield is up to 25 verified business websites per city. Max 30.",
                        "default": 25
                    },
                    "minimumAiConfidence": {
                        "title": "Minimum AI confidence threshold",
                        "minimum": 0.4,
                        "maximum": 0.9,
                        "type": "number",
                        "description": "Domains scoring below this threshold are excluded and not charged. 0.6 is recommended. 0.8 or above gives higher precision with fewer results. 0.4 gives maximum coverage with occasional noise.",
                        "default": 0.6
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
