# North Carolina Plumbing & HVAC Contractor Leads (`automation-lab/north-carolina-plumbing-hvac-contractor-leads`) Actor

Search the official North Carolina plumbing, heating, and fire-sprinkler contractor registry and export public license, business, contact, classification, and query-provenance records.

- **URL**: https://apify.com/automation-lab/north-carolina-plumbing-hvac-contractor-leads.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.92 / 1,000 item extracteds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — 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

## North Carolina Plumbing & HVAC Contractor Leads

Build repeatable **North Carolina plumbing HVAC contractor leads** lists from the official State Board of Examiners of Plumbing, Heating & Fire Sprinkler Contractors registry.

Search by company name, North Carolina city, or exact license account number. Each result combines the board's search identity with its public license detail: account number, record type, contact or business, address, phone, expiration date, active classifications, classification holders, query provenance, and retrieval time.

This Actor is designed for targeted, recurring prospect-list refreshes. It does not claim an unbounded statewide roster export.

### What can you do with this Actor?

- Build a call list of licensed plumbing businesses containing a company keyword.
- Find HVAC contractors in a city and filter them by an active heating classification.
- Verify an exact North Carolina license account before onboarding a vendor.
- Refresh the same search on a schedule and compare datasets downstream.
- Send structured rows to Google Sheets, Airtable, a CRM, a data warehouse, or your own API.

### Who is it for?

**Trade suppliers** can identify licensed plumbing and HVAC businesses in a sales territory.

**Contractor software teams** can build a focused, license-backed prospect list instead of relying only on general directories.

**Compliance and procurement teams** can verify the public record attached to a known license number.

**Market researchers** can compare official business identities and active classifications across targeted cities or company-name searches.

### Why use the official North Carolina registry?

The Actor reads `public.nclicensing.org`, the public portal of the North Carolina board for plumbing, heating, and fire-sprinkler contractors.

That gives every row clear source provenance. It also exposes fields that a general business directory may omit, including the board account type, expiration date, active license classifications, and named classification holders.

The portal is an operational public service and can be intermittent. The Actor uses a coherent US proxy session, bootstrap cookies, bounded retries, and conservative detail concurrency. It fails rather than silently returning a challenge page as an empty result.

### Data extracted

| Field | Meaning |
| --- | --- |
| `accountNumber` | Official board license account number, such as `L.00327` |
| `recordType` | Board account type, such as `License` or `Sub-License` |
| `ownerOrBusinessName` | Identity displayed in the board's result table |
| `contactName` | Public contact name from the detail record |
| `companyName` | Public company name from the detail record |
| `address` | Public mailing address displayed by the board |
| `phone` | Public phone number displayed by the board |
| `expirationDate` | Expiration date in the board's displayed format |
| `activeClassifications` | Current classifications listed in the active-classifications section |
| `classificationHolders` | Public holder name and classification strings |
| `queryType` | Whether company, city, or license search found the record |
| `queryValue` | Exact input value that found the record |
| `classificationFilters` | Detail-level classification filters applied by the run |
| `sourceUrl` | Official registry search URL |
| `retrievedAt` | ISO 8601 time at which the run retrieved records |

Fields may be `null` when the board does not publish that value for an account. Arrays are empty when no corresponding values are displayed.

### Getting started

1. Open the Actor input.
2. Add at least one company name, city, or license number.
3. Optionally add active-classification phrases such as `Plumbing Class I` or `Heating Group 3`.
4. Set the maximum number of matching records.
5. Keep the recommended residential US proxy unless you have tested another route.
6. Click **Start**.
7. Open the Dataset tab to download JSON, CSV, Excel, XML, or another supported Apify format.

A useful first run is:

```json
{
  "companyNames": ["plumbing"],
  "maxItems": 10
}
```

### Input parameters

#### Search fields

`companyNames` is a list of terms sent to the board's company-name field. The source performs a containing-name search.

`cities` is a list of North Carolina mailing-address cities. Use names such as `Raleigh`, `Charlotte`, or `Durham`.

`licenseNumbers` is a list of exact account numbers. Both `L.00327` and `00327` are accepted.

At least one value across these three fields is required. Up to 50 combined search values are accepted in one run.

#### Detail filtering

`classifications` is an optional list of case-insensitive phrases. A record is kept when at least one active classification contains one of the phrases.

For example, `Heating Group 3` matches both Class I and Class II Heating Group 3 contractor records.

Classification filtering occurs against the public detail record, not against guessed words in a company name.

#### Limits and connection

`maxItems` defaults to 100 and can be set from 1 to 5,000.

`maxConcurrency` defaults to 4 and can be set from 1 to 10. Conservative values reduce pressure on the official portal.

`proxyConfiguration` defaults to Apify Residential Proxy in the United States. Disabling it may work from some networks, but the portal intermittently rejects or fails repeated connections from others.

### Output example

A real current-source shape, with the public identity shortened for documentation, looks like this:

```json
{
  "accountNumber": "L.00327",
  "recordType": "License",
  "ownerOrBusinessName": "Donaldson Plumbing, Inc.",
  "contactName": "Steven John Donaldson",
  "companyName": "Donaldson Plumbing, Inc.",
  "address": "3613 Cedar Bark Dr, Matthews, NC 28105",
  "phone": "(704) 995-5669",
  "expirationDate": "12/31/2026",
  "activeClassifications": [
    "Plumbing Class II Contractor"
  ],
  "classificationHolders": [
    "Steven John Donaldson: Plumbing Class II Contractor"
  ],
  "queryType": "licenseNumber",
  "queryValue": "L.00327",
  "classificationFilters": [],
  "sourceUrl": "https://public.nclicensing.org/Public/Search",
  "retrievedAt": "2026-09-06T20:00:00.000Z"
}
```

The default dataset contains one row per unique account number accepted by the current run.

### How much does it cost to export North Carolina contractor leads?

Pay-per-event pricing has a **$0.005 Actor start fee** plus a tiered charge for each contractor record saved to the dataset.

Current per-record prices are:

| Plan | Price per saved contractor record |
| --- | ---: |
| Free | $0.00368 |
| Bronze | $0.00320 |
| Silver | $0.002496 |
| Gold | $0.00192 |
| Platinum | $0.00192 |
| Diamond | $0.00192 |

At Bronze rates, 10 saved records cost about **$0.037**, including the start fee. One hundred records cost about **$0.325**. Filters that reject a detail are not charged as output items.

Apify platform usage and proxy transfer are covered by the event prices rather than billed as a separate Actor event. Your Apify plan and platform terms still apply.

### Reliable recurring prospect-list refreshes

Save one focused search as an Apify Task, then schedule it daily, weekly, or monthly.

A typical Raleigh HVAC input is:

```json
{
  "cities": ["Raleigh"],
  "classifications": ["Heating Group 3"],
  "maxItems": 100
}
```

Each row includes `queryValue` and `retrievedAt`, making it straightforward to compare run datasets in a warehouse or automation platform.

The Actor itself does not maintain a historical database, calculate changes, or send alerts. Use schedules plus downstream dataset comparison for those workflows.

### Integrations and exports

Use Apify integrations to send completed datasets to Google Sheets, Make, Zapier, Slack, webhooks, or custom workflows.

For a CRM refresh:

1. Schedule one task per territory or buyer segment.
2. Use `accountNumber` as the stable source key.
3. Upsert company, contact, phone, address, and classifications.
4. Store `retrievedAt` as the source refresh timestamp.
5. Review source changes before using them for compliance decisions.

For spreadsheet analysis, open the run dataset and choose CSV or Excel from the export controls.

### Run through the API

Replace `YOUR_APIFY_TOKEN` with your token and keep it outside source control.

#### cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~north-carolina-plumbing-hvac-contractor-leads/runs?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"companyNames":["plumbing"],"maxItems":10}'
```

#### JavaScript

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor(
  'automation-lab/north-carolina-plumbing-hvac-contractor-leads',
).call({ companyNames: ['plumbing'], maxItems: 10 });

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor(
    "automation-lab/north-carolina-plumbing-hvac-contractor-leads"
).call(run_input={"cities": ["Raleigh"], "maxItems": 25})

items = client.dataset(run["defaultDatasetId"]).list_items().items
print(items)
```

### Use with MCP and AI assistants

Add the Actor to Claude Code through Apify MCP:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/north-carolina-plumbing-hvac-contractor-leads"
```

#### Claude Desktop, Cursor, and VS Code setup

Claude Desktop, Cursor, and VS Code can use this equivalent MCP configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/north-carolina-plumbing-hvac-contractor-leads"
    }
  }
}
```

Example prompts:

- “Find up to 25 North Carolina plumbing businesses whose company name contains plumbing.”
- “Verify license L.00327 and summarize its active classifications and expiration.”
- “Export Raleigh records with an active Heating Group 3 classification.”

### Limits and failure behavior

- The Actor searches company name, city, or exact license account number; it does not perform an unbounded statewide sweep.
- City matches use the address data and behavior of the official portal.
- Classification filtering requires detail requests and can inspect more candidates than the final saved count.
- The registry may contain masked, historical, incomplete, or missing values.
- Expiration and classification values are reported as displayed; the Actor does not provide legal conclusions.
- The upstream portal can be slow or unavailable. Transient requests are retried three times with bounded backoff.
- A rejected input, challenge page, or exhausted upstream failure makes the run fail instead of returning a misleading successful empty dataset.
- A legitimate no-match search completes successfully with zero dataset rows.

### Responsible use and legality

This Actor accesses publicly displayed government licensing information. Public availability does not remove your responsibilities.

Follow applicable laws, the source site's terms, Apify's terms, privacy rules, outreach rules, and anti-spam requirements. Collect only data needed for a legitimate purpose. Secure exported contact data, limit retention, honor applicable requests, and verify important licensing decisions directly with the board.

The Actor is an independent automation tool and is not affiliated with or endorsed by the North Carolina licensing board.

### FAQ and troubleshooting

#### Why did my run return no records?

Check spelling and try a broader company term. Confirm that the city is the mailing-address city used by the board. Remove classification filters to see whether the search found candidates that simply lacked your requested active classification.

#### Why did my run fail on a connection error?

Keep the default US residential proxy and reduce `maxConcurrency`. The official portal can be intermittent. The Actor already retries transient failures; repeated exhaustion usually means the source is temporarily unavailable.

#### Why is a public field null?

Different account types expose different details. Null means that the corresponding value was not displayed in the parsed public detail record.

#### Does the Actor enrich emails or websites?

No. It returns fields published by the official registry. It does not crawl business websites or infer contact details.

#### Can it monitor changes automatically?

The Actor returns current rows and provenance. Create a scheduled Task and compare datasets downstream; built-in history and alerts are not claimed.

### Related automation-lab Actors

- [Texas Insurance License Lookup](https://apify.com/automation-lab/texas-insurance-license-lookup) for official Texas insurance producer and agency records.
- [Texas Pharmacy License Lookup](https://apify.com/automation-lab/texas-pharmacy-license-lookup) for official Texas pharmacy credential records.
- [Virginia Real Estate License Lookup](https://apify.com/automation-lab/virginia-real-estate-license-lookup) for Virginia DPOR real-estate regulant files.

These are separate sources and professions; use the Actor matching the official registry you need.

# Actor input Schema

## `companyNames` (type: `array`):

Company-name terms to search. The board returns names containing each term.

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

North Carolina mailing-address cities to search, such as Raleigh or Charlotte.

## `licenseNumbers` (type: `array`):

Exact board license account numbers, with or without the L. prefix.

## `classifications` (type: `array`):

Keep records whose active classification contains at least one supplied phrase, such as Plumbing Class I or Heating Group 3.

## `maxItems` (type: `integer`):

Stop after this many unique, matching contractor records.

## `maxConcurrency` (type: `integer`):

Maximum simultaneous official detail-page requests. Lower this if the registry is under load.

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

Residential US proxy is recommended because the official registry rate-limits repeated requests from some networks.

## Actor input object example

```json
{
  "companyNames": [
    "plumbing"
  ],
  "maxItems": 10,
  "maxConcurrency": 4,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset containing the normalized contractor license records.

# 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 = {
    "companyNames": [
        "plumbing"
    ],
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/north-carolina-plumbing-hvac-contractor-leads").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 = {
    "companyNames": ["plumbing"],
    "maxItems": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/north-carolina-plumbing-hvac-contractor-leads").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 '{
  "companyNames": [
    "plumbing"
  ],
  "maxItems": 10
}' |
apify call automation-lab/north-carolina-plumbing-hvac-contractor-leads --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/north-carolina-plumbing-hvac-contractor-leads"
        }
    }
}

```

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/77jKplpf3Gvpfqgos/builds/1NQvcmbsFNxvBghqq/openapi.json
