# HVAC Contractor Directory & Leads (State License Boards) (`scrapebench/hvac-contractor-directory-leads-scraper`) Actor

Export active HVAC contractor licences from five state boards — Texas, Ohio, West Virginia, Idaho and Delaware (27,635 on 2026-10-15) — in one list, with the phone and address each board publishes.

- **URL**: https://apify.com/scrapebench/hvac-contractor-directory-leads-scraper.md
- **Developed by:** [ScrapeBench](https://apify.com/scrapebench) (community)
- **Categories:** Lead generation
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.002 / hvac contractor licence

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## HVAC Contractor Directory & Leads (State License Boards)

### Pain points

- HVAC is licensed by a different board in every state, each with its own site and format: an open-data table in Texas, a roster CSV built on request in Ohio, a paged search in West Virginia, one spreadsheet per licence type in Idaho, and a dataset of every profession in Delaware.
- The Texas table holds about a million TDLR licences of every trade; A/C Contractors are 20,436 rows of it, and 1,776 of those had already expired on 2026-10-15.
- What each board publishes differs: West Virginia lists a phone for 1,636 of its 1,637 HVAC contractors, while Texas publishes no address, city or phone for A/C Contractors at all.

### What we solve

- One run exports the active HVAC contractor licences of all five boards into one table with the same columns, or any subset of states.
- Only active or current licences: expired Texas licences, lapsed Idaho ones and closed Delaware ones are left out.
- Narrow by county, city or ZIP, each with a batch list; maxResults caps every state separately, so a sample run gives you rows from each board.

### Summary

Active HVAC contractor licences from five state licensing boards in one export: Texas (TDLR A/C Contractors), Ohio (OCILB HVAC, refrigeration and hydronics contractors), West Virginia (WVCLB licences with the HVAC classification), Idaho (DOPL HVAC contractor licences) and Delaware (DPR Master HVACR). 27,635 licences on 2026-10-15. Every row has the state, board, licence number and type, holder name and expiry, plus whatever contact data that board publishes. Each board is read live on every run. Pay per licence returned.

### Who it's for

- HVAC equipment and parts distributors building contractor prospect lists
- Sales reps selling software, financing, insurance or training to HVAC contractors
- General contractors and property managers sourcing licensed HVAC subs
- Lead-generation agencies building HVAC lists by county

### How to use

Set the input, run the actor, and collect results from the run's dataset (export to JSON/CSV/Excel, or pull via the Apify API). Example input:

```json
{
  "states": [
    "OH"
  ],
  "counties": [
    "Franklin"
  ],
  "maxResults": 1000
}
```

See **Inputs** below for every available field.

### What you get

One row per record:

| Field | Description |
|---|---|
| `state` | Licensing state: TX, OH, WV, ID or DE |
| `board` | Licensing board: TDLR, OCILB, WVCLB, DOPL or DPR |
| `business_name` | Licensed company, or the holder's name where the board lists a person |
| `person_name` | Licensed individual or qualifying party, when published |
| `phone` | Business phone, when the board publishes one |
| `address` | Street or mailing address (OH, WV, ID) |
| `city` | City (OH, WV, ID, DE) |
| `zip_code` | ZIP or ZIP+4 |
| `county` | County as the board lists it (TX, OH, WV, ID) |
| `license_type` | The board's licence type, e.g. A/C Contractor, HVAC Contractor, Master HVACR |
| `license_number` | The board's licence number |
| `expiration_date` | Licence expiry (YYYY-MM-DD) |
| `source_url` | The board page or dataset the row came from |

Sample:

```json
{
  "state": "OH",
  "board": "OCILB",
  "license_number": "HV.10704",
  "license_type": "HVAC Contractor",
  "status": "ACTIVE IN RENEWAL",
  "business_name": "DOOLEY HEATING & AIR CONDITIONING",
  "person_name": "BRIAN P DOOLEY",
  "phone": "(614) 278-9944",
  "email": null,
  "address": "2010 Zettler Rd",
  "city": "Columbus",
  "address_state": "OH",
  "zip_code": "43232-3834",
  "county": "Franklin",
  "issue_date": "2026-01-01",
  "expiration_date": "2026-12-31",
  "trade": "hvac",
  "matched_by": "export",
  "source_url": "https://elicense4.com.ohio.gov/Lookup/GenerateRoster.aspx"
}
```

### Inputs

| Field | Required | Type | Default | Description |
|---|---|---|---|---|
| `states` | no | array | `["OH", "WV"]` | Which boards to export: TX, OH, WV, ID, DE (codes or names). Empty = all five. |
| `state` | no | string | — | One state, as a code or name. Kept for saved tasks; 'States' takes a list. |
| `county` | no | string | — | A county name, matched in every selected state (so pick the state too: Franklin County exists in Ohio, Texas and Idaho). Delaware's board publishes no county, so a county filter returns no Delaware rows. |
| `counties` | no | array | `[]` | Several counties in ONE run, e.g. Franklin, Delaware, Licking. |
| `city` | no | string | — | Business or mailing city, exact match ignoring case and punctuation. Texas publishes no city for A/C Contractors, so a city filter returns no Texas rows. |
| `cities` | no | array | `[]` | Several cities in ONE run. |
| `zip` | no | string | — | A 5-digit ZIP (ZIP+4 accepted; the first 5 digits are matched). Texas publishes no ZIP for A/C Contractors. |
| `zips` | no | array | `[]` | Several ZIP codes in ONE run. |
| `maxResults` | no | integer | `100` | Cap on licences for EACH selected state — your spend control. Every active HVAC licence across the five boards was 27,635 on 2026-10-15 (TX 18,660, OH 4,850, WV 1,637, ID 1,249, DE 1,239); set 100000 to take them all. |
| `proxyConfiguration` | no | object | `{"useApifyProxy": false}` | Not needed: every board answers Apify's own IPs (each one runs live in its own state actor). Leave it off unless a run reports that a board refused it. |

### Pricing (Pay Per Event)

You pay per result (`dataset-item`) — **no charge for empty runs**. Example: **302 Franklin County, Ohio HVAC contractors** at *$0.002/result* ≈ **$0.60**.

The default run (100 licences per state, all five states) costs $1.00; every active licence in the five states (27,635 on 2026-10-15) costs $55.27 at maxResults 100000. Apify platform usage is billed separately.

### Use cases

- Distributor prospecting: an equipment distributor exports every HVAC contractor in Franklin County, Ohio with phone and address for a territory push.
- Multi-state list build: a software vendor takes all five states once, then re-runs monthly and compares licence numbers to find new licensees.
- Phone-ready outreach: a sales team exports West Virginia, where nearly every HVAC licence carries a phone number, for a calling campaign.
- Sub sourcing: a general contractor lists the licensed HVAC contractors in one Idaho county before sending bid invitations.

### Why this actor

- 27,635 active HVAC licences across five boards in one table with one set of columns (measured 2026-10-15).
- Each board is read live on every run from its own public site or open-data portal.
- A board that does not answer becomes a free notice row while the other states still deliver; a filter value it does not recognise is dropped and logged, never widened to everything.

### Limitations & updates

Five states only (TX, OH, WV, ID, DE); more may be added. Contact fields are whatever each board publishes, and Texas publishes no address or phone for A/C Contractors. Texas and Delaware licences are held by individuals, so business_name is often a person's name there. Ohio licenses commercial trade contractors only. Addresses are as filed and can be PO boxes or out-of-state offices.

### FAQ

**Which states and licences are included?**

Texas: TDLR A/C Contractor licences (18,660 current). Ohio: OCILB HVAC, refrigeration and hydronics contractor licences (4,850). West Virginia: WVCLB licences carrying the HVAC classification (1,637). Idaho: DOPL HVAC Contractor, HVAC Specialty and HVAC Limited Heating Specialty licences (1,249). Delaware: DPR Master HVACR and Master HVACR Restricted licences (1,239). Counts from 2026-10-15.

**Which contact fields will I get?**

Whatever each board publishes, measured 2026-10-15. Phone: WV 1,636 of 1,637, OH 3,125 of 4,850, ID 46 of 1,249, TX and DE none. Street address: OH 4,819, WV all, ID 734, TX and DE none. City and ZIP: OH, WV, ID (734) and DE (all); Texas publishes none for A/C Contractors, only the county (17,988 of 18,660).

**Does it include expired licences?**

No. Texas licences past their expiry date, Idaho licences not Active and Delaware licences not Active are left out; Ohio's roster and West Virginia's search list live licences only.

**How do filters work across states?**

A county, city or ZIP is matched in every state you select, so pick the state too: Franklin County exists in Ohio, Texas and Idaho. A state whose board does not publish a field cannot match a filter on it (no Texas rows for a city or ZIP filter, no Delaware rows for a county filter), and the run log says so.

**How am I charged?**

$0.002 per HVAC licence returned. maxResults caps each state separately: the default 100 per state across all five is 500 licences ($1.00). Franklin County, Ohio was 302 ($0.60) and all five states 27,635 ($55.27) on 2026-10-15. Rows explaining an outage, an unrecognised filter, no match or a reached spending cap are free.

### Which actor to choose

Need every trade, not just HVAC? Each state has its own lookup:

- **Texas Contractor & Trade License Lookup & Export (TDLR)** — You want Texas electrical, A/C and other TDLR trade licences, or a lookup by licence number or name.
- **Ohio Contractor License Lookup & Export (OCILB)** — You want Ohio electrical and plumbing contractors as well as HVAC.
- **West Virginia Contractor License Lookup & Export (WVCLB)** — You want any West Virginia classification, or a lookup by licence number or company name.

### Guides & use cases

Written up on **[scrapebench.dev](https://scrapebench.dev)** — the bench that runs and verifies this actor against the live source every night:

- **How-to:** [How to run HVAC Contractor Directory & Leads (State License Boards)](https://scrapebench.dev/guides/how-to-hvac-contractor-directory-leads-scraper/)
- **Use case:** [Export Every Active HVAC Contractor in TX, OH, WV, ID and DE](https://scrapebench.dev/use-cases/hvac-contractors-all-states/)

More actors, coverage and nightly verification results: **[scrapebench.dev](https://scrapebench.dev)**

### Works with AI assistants (MCP)

Callable as an MCP tool, so Claude, Cursor, VS Code Copilot and other MCP clients can run it directly. Grab the config from the [MCP tab](https://apify.com/scrapebench/hvac-contractor-directory-leads-scraper/api/mcp) on this page — Apify hosts the server and keeps that snippet current, and OAuth signs you in on first connect, so no API token goes in your config file.

Then just ask:

> "List the licensed HVAC contractors in Franklin County, Ohio, with phone and address."

That input returned 302 licences on 2026-10-15. Leave the filters empty to export every state. Runs started this way bill exactly like any other run.

# Actor input Schema

## `states` (type: `array`):

Which boards to export: TX, OH, WV, ID, DE (codes or names). Empty = all five.

## `state` (type: `string`):

One state, as a code or name. Kept for saved tasks; 'States' takes a list.

## `county` (type: `string`):

A county name, matched in every selected state (so pick the state too: Franklin County exists in Ohio, Texas and Idaho). Delaware's board publishes no county, so a county filter returns no Delaware rows.

## `counties` (type: `array`):

Several counties in ONE run, e.g. Franklin, Delaware, Licking.

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

Business or mailing city, exact match ignoring case and punctuation. Texas publishes no city for A/C Contractors, so a city filter returns no Texas rows.

## `cities` (type: `array`):

Several cities in ONE run.

## `zip` (type: `string`):

A 5-digit ZIP (ZIP+4 accepted; the first 5 digits are matched). Texas publishes no ZIP for A/C Contractors.

## `zips` (type: `array`):

Several ZIP codes in ONE run.

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

Cap on licences for EACH selected state — your spend control. Every active HVAC licence across the five boards was 27,635 on 2026-10-15 (TX 18,660, OH 4,850, WV 1,637, ID 1,249, DE 1,239); set 100000 to take them all.

## `proxyConfiguration` (type: `object`):

Not needed: every board answers Apify's own IPs (each one runs live in its own state actor). Leave it off unless a run reports that a board refused it.

## Actor input object example

```json
{
  "states": [
    "OH",
    "WV"
  ],
  "county": "Franklin",
  "counties": [],
  "cities": [],
  "zips": [],
  "maxResults": 100,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Each row is one active HVAC licence: state, board, company, contact fields the board publishes, county, licence type, number and expiry.

# 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 = {
    "states": [
        "OH",
        "WV"
    ],
    "maxResults": 100,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapebench/hvac-contractor-directory-leads-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "states": [
        "OH",
        "WV",
    ],
    "maxResults": 100,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapebench/hvac-contractor-directory-leads-scraper").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "states": [
    "OH",
    "WV"
  ],
  "maxResults": 100,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call scrapebench/hvac-contractor-directory-leads-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapebench/hvac-contractor-directory-leads-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/xD8jmlzYSnGpGjEIC/builds/36PA3yWdT2YsHHXiF/openapi.json
