# B2B Lead Lists by City - Dentists, Lawyers, HVAC (`tidytools/b2b-lead-lists`) Actor

Prebuilt public business contacts by US or Canadian city. Search a niche and city, receive source links and refresh dates; optional live contact extraction. Charged per contact row.

- **URL**: https://apify.com/tidytools/b2b-lead-lists.md
- **Developed by:** [Yukai Lin](https://apify.com/tidytools) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 business contact rows

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

## B2B Lead Lists by City

### What it does

Get a prebuilt snapshot of public business contacts for a niche and city in the US or Canada. The result includes the date each row was fetched and a company website or contact page to check the record. The list is a starting point for research; verify contacts before outreach.

### How to use

Choose a niche and enter a city with `US` or `CA`. Start with a small `maxResults` to inspect the data. The Console prefill requests two Austin dentist contacts from the prebuilt snapshot (`buildIfMissing: false`); successful rows are charged at the existing `lead-row` rate. Replace the example before requesting another list. If the city has no snapshot, the Actor fetches live contacts from company websites (set `buildIfMissing` to false to get prebuilt snapshots only).

### Input

`niche`: `dentist`, `lawyer`, `hvac`, `gym`, or `realtor`. `city`: a city with its country, US state or Canadian province, for example `Austin, US`, `Austin, TX`, `Toronto, ON` or `Toronto, Canada`. `City, CA` means California unless the city is a well-known Canadian city (`Toronto, CA` is Canada, `Los Angeles, CA` is California). `maxResults`: 1–500, default 100. `buildIfMissing`: default `true`; when true, search places and extract public contacts from company websites if no prebuilt snapshot exists. This live mode can take longer, scans up to 10 candidate websites per requested row (500 candidates at most), and yields up to 500 rows.

### Output and price

Every contact row contains `id`, `name`, `website`, `phone`, `emails`, `address`, `sourcePage`, `sourceDataset`, `sourceUpdated`, `fetchedAt`, `fromIndex`, `license`, `attribution`, `niche`, `city`, `success`, `charged`, `chargeEvent`. A prebuilt row has `fromIndex: true`; a live row has `fromIndex: false`. The `SUMMARY` store record gives `status`, `mode`, `rows`, `charged`, `chargeEvent`, and `fetchedAt` (snapshot time or null for live mode). An invalid input, missing snapshot without live mode, or empty result yields a free explanation row with `success: false`, `errorType`, `error`, `charged: false`, `niche` and `city` where available.

Prebuilt results use `lead-row` at $0.002 per row ($2 per 1,000) (volume discounts by plan). A row counts only if it has a phone or email. Live results use `site-processed` at $0.002 per website only when that website yields an email or phone; a directory phone alone or a social profile does not qualify. Each row's `charged` and `chargeEvent` reflect the confirmed charge. If the charge limit is reached or charging fails, the current row is marked uncharged and the run stops producing rows. `SUMMARY.charged` counts confirmed events. No prebuilt list means no prebuilt charge.

Source: Overture Maps places (CDLA-Permissive-2.0, © Overture Maps Foundation), plus public company website contacts. For live rows, `sourcePage` is the first reported email page when available, otherwise the business website; for snapshots it is the recorded source page. Live rows use the website's own phone when it shows one; otherwise the phone can come from Overture rather than that page. `sourceUpdated` is the source record date when available. `fetchedAt` is our capture time, not a promise that the business still operates. Only US and Canada are supported. Use contact data in line with applicable outreach and privacy law.

### Examples

```json
{"niche":"dentist","city":"Austin, US","maxResults":2,"buildIfMissing":false}
```

```json
{"niche":"lawyer","city":"Toronto, CA","maxResults":3,"buildIfMissing":true}
```

```json
{"niche":"hvac","city":"Chicago, US","maxResults":2,"buildIfMissing":false}
```

### Output example

```json
{"id":"example-1","name":"Example Dental","website":"https://example.org/","phone":"+15125550100","emails":["info@example.org"],"address":"Austin, TX","sourcePage":"https://example.org/contact","sourceDataset":"Overture Maps Places","sourceUpdated":null,"fetchedAt":"2026-10-23T00:00:00.000Z","fromIndex":true,"license":"CDLA-Permissive-2.0","attribution":"© Overture Maps Foundation (places)","niche":"dentist","city":"Austin, US","success":true,"charged":true,"chargeEvent":"lead-row"}
```

### Limitations

Prebuilt rows only contain phones and emails that were seen on the company's own website at `fetchedAt`; businesses whose website could not be read or showed no contact are left out (in our October 2026 check, 19/19, 18/19 and 18/19 sampled rows were correct for Austin dentists, Chicago lawyers and Houston HVAC). Categories come from Overture Maps and can include neighbouring businesses, for example court-reporting firms under lawyers or HVAC manufacturers under HVAC. Snapshots exist only for cities that have been built. Website and phone records can go stale between refreshes. A phone from Overture can differ from the business website. Live extraction reads up to three pages per site and may miss contacts on blocked or script-heavy pages.

### FAQ

**What happens when no snapshot exists?** By default the Actor runs a live lookup; with `buildIfMissing: false` you receive a free explanation row instead.

**Are the contacts verified?** No. Check the source page and current business status before use.

# Actor input Schema

## `niche` (type: `string`):

Choose one of the supported business categories. Prebuilt availability depends on the city.

## `city` (type: `string`):

City with country, state or province: Austin, US / Austin, TX / Toronto, ON / Toronto, Canada. "City, CA" means California unless the city is a well-known Canadian city (Toronto, CA = Canada). Only US and Canada are supported.

## `maxResults` (type: `integer`):

Upper bound on companies returned and charged in this run.

## `buildIfMissing` (type: `boolean`):

If no snapshot exists, search Overture places and visit business websites for contacts. Live rows use the site-processed event instead of lead-row. Turn off to get only prebuilt snapshots.

## Actor input object example

```json
{
  "niche": "dentist",
  "city": "Austin, US",
  "maxResults": 2,
  "buildIfMissing": true
}
```

# Actor output Schema

## `rows` (type: `string`):

No description

## `summary` (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 = {
    "niche": "dentist",
    "city": "Austin, US",
    "maxResults": 2,
    "buildIfMissing": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("tidytools/b2b-lead-lists").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 = {
    "niche": "dentist",
    "city": "Austin, US",
    "maxResults": 2,
    "buildIfMissing": False,
}

# Run the Actor and wait for it to finish
run = client.actor("tidytools/b2b-lead-lists").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 '{
  "niche": "dentist",
  "city": "Austin, US",
  "maxResults": 2,
  "buildIfMissing": false
}' |
apify call tidytools/b2b-lead-lists --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tidytools/b2b-lead-lists"
        }
    }
}
```

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/v3wb5CY0PgzZPWKWE/builds/KdcUgFn0mJF5mCYI4/openapi.json
