# Utah Contractor Directory & Leads Scraper (DOPL) (`scrapebench/utah-contractor-directory-leads-scraper`) Actor

Utah contractors, electricians and plumbers listed in DOPL's Construction Business Registry, by trade and county or city: phone, email, licence number, address and qualifier.

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

## Pricing

$0.002 / contractor record

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

## Utah Contractor Directory & Leads Scraper (DOPL)

### Pain points

- The registry's search table shows only name, phone, email and city. The licence number, expiry, street address, ZIP and qualifier sit behind a separate Details pop-up, one listing at a time.
- There is no county filter, only a City box that matches any city containing the text: 'Salt Lake' also returns North Salt Lake and South Salt Lake.
- Choosing Contractor / ALL CONTRACTORS also returns the registry's electricians, plumbers, burglar alarm and elevator licensees: 5,849 rows, of which 5,032 are contractors (counted 2026-10-06).

### What we solve

- Pick a classification and a county or city and get every matching registry listing in one run, with phone, email, licence number, address and qualifier, ready for a CRM.
- Works out each listing's county from its city using the US Census Bureau's 2020 place-to-county table, and says how on every row (county_method).
- Keeps only the category you asked for, filters cities exactly, and returns a business once even when two of your searches both match it.

### Summary

Build a Utah contractor prospect list in one run from the Division of Professional Licensing's Construction Business Registry (CBR). Choose a classification, such as S280 Roofing, S350 HVAC, B100 General Building or any of DOPL's 25 contractor classifications, or electricians and plumbers by licence level, then a county (or several) or a city. You get every matching listing: business name, phone, email, city, county, street address, ZIP, licence number, licence type, expiry date, and the qualifier named on the licence with their classifications. The CBR lists only businesses with an active licence that chose to opt in, so it is a directory of contractors who want to be contacted, not the full DOPL licence roll. Pay only per listing returned.

### Who it's for

- Sales teams selling materials, equipment, software or financing to Utah contractors
- Insurance and bonding agents prospecting for contractor accounts by county
- General contractors building a subcontractor shortlist by classification before bidding
- Home-services marketplaces recruiting licensed Utah pros who have opted in to be contacted

### 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
{
  "county": "Salt Lake",
  "trade": "Roofing"
}
```

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

### What you get

One row per record:

| Field | Description |
|---|---|
| `business_name` | Business (or, for electricians and plumbers, licensee) name as listed |
| `phone` | Phone number the licensee gave the registry |
| `email` | Email address the licensee gave the registry |
| `city` | City the licensee listed |
| `county` | Utah county, worked out from the city (see county_method) |
| `county_method` | city, city_primary (city spans two counties; the one most of it lies in), or null (city not recognised) |
| `address` | Street address, when the licensee gave one |
| `zip_code` | ZIP code from the listing's address |
| `license_number` | DOPL licence number, e.g. 14229585-5501 |
| `license_type` | Licence type, e.g. Contractor, Master Electrician |
| `expiration_date` | Licence expiry date (MM/DD/YYYY) |
| `person_name` | Qualifier(s) named on the licence |
| `classifications` | The qualifiers' DOPL classifications, e.g. 'S280 - Roofing Qualifier' |
| `qualifiers` | Each qualifier with their classification |
| `category` | Registry category: Contractor, Electrician, Plumber, Burglar Alarm, Elevator Mechanic |
| `cbr_id` | The registry's own ID for the listing |
| `enrichment_status` | detail, detail_unavailable or skipped_time_budget: why licence and address fields may be null |
| `requested_trade` | The classification search that returned the row |
| `requested_county` | The county search that returned the row |
| `source_url` | The Construction Business Registry |

Sample:

```json
{
  "business_name": "Imperial Custom Sheet Metal & Roofing LLC",
  "phone": "(385) 227-7041",
  "email": "isagon1998@gmail.com",
  "city": "West Valley City",
  "county": "Salt Lake",
  "county_method": "city",
  "address": "5279 Woodstep Avenue",
  "zip_code": "84120",
  "state": "UT",
  "license_number": "14229585-5501",
  "license_type": "Contractor",
  "expiration_date": "11/30/2027",
  "person_name": "Isabel Gonzalez",
  "classifications": "S280 - Roofing Qualifier; S220 - Carpentry and Flooring Qualifier",
  "category": "Contractor",
  "cbr_id": "7499119",
  "enrichment_status": "detail",
  "requested_trade": "S280 - Roofing Contractor",
  "requested_county": "Salt Lake",
  "source_url": "https://services.commerce.utah.gov/cbr/"
}
```

### Inputs

| Field | Required | Type | Default | Description |
|---|---|---|---|---|
| `listOptionsOnly` | no | boolean | `false` | If true, skip the scrape and return Utah's 29 counties plus every category and classification the Construction Business Registry (CBR) searches by, with its code. Free, not charged. |
| `trade` | no | string | `"Roofing"` | A DOPL classification by name or code, e.g. 'Roofing', 'S280', 'HVAC', 'General Building', 'B100'. A word from the name works too: 'roof' finds S280 Roofing. For electricians and plumbers use the licence level: 'Master', 'Journeyman', 'Apprentice'. Leave empty for every classification. |
| `trades` | no | array | `["Roofing", "HVAC"]` | Several classifications in ONE run, e.g. Roofing and HVAC. Combined with 'Trade' if you fill both. A business listed under two of them is returned (and billed) once. |
| `category` | no | string | — | Which part of the registry: 'contractor' (default), 'handyman', 'electrician', 'plumber', 'burglar alarm' or 'elevator mechanic'. Electricians and plumbers are mostly individual licensees. |
| `categories` | no | array | `[]` | Several categories in ONE run. Combined with 'Category' if you fill both. |
| `county` | no | string | `"Salt Lake"` | Utah county, e.g. 'Salt Lake', 'Utah County', 'washington'. The registry has no county field, so county is worked out from each listing's city (see county_method). Leave empty for all of Utah. If it is not a Utah county, the run returns the free county and classification lists instead of failing. |
| `counties` | no | array | `["Salt Lake", "Utah"]` | Several counties in ONE run, e.g. Salt Lake and Utah and Davis. Combined with 'County' if you fill both. |
| `city` | no | string | — | A Utah city or town, e.g. 'Provo', 'St. George'. Exact city match ('Salt Lake City' does not include North Salt Lake). Use instead of, or as well as, a county. |
| `cities` | no | array | `[]` | Several cities in ONE run. Combined with 'City' if you fill both. |
| `includeDetails` | no | boolean | `true` | Fetch each listing's detail record: licence number, licence type, expiry, street address, ZIP and qualifier names. One extra request per listing; turn off for a faster run with name, phone, email and city only. |
| `maxResults` | no | integer | — | Cap on the listings returned PER search (each classification x each county or city), not per run, so adding places can never silently truncate the later ones. Leave empty to return every match. |
| `proxyConfiguration` | no | object | `{"useApifyProxy": false}` | Optional. The registry answers direct requests; enable a proxy only if runs come back with a 'blocked' notice. |

### Pricing (Pay Per Event)

You pay per result (`dataset-item`) — **no charge for empty runs**. Example: **63 Salt Lake County roofing contractors** at *$0.002/result* ≈ **$0.13**.

Discovery Mode and unrecognised inputs return the free county and classification lists. Apify platform usage is billed separately.

### Use cases

- Territory prospecting: pull every roofing, HVAC or general building contractor listed in Salt Lake, Utah and Davis counties and load the phone and email list into your CRM.
- Subcontractor sourcing: list every registry contractor holding a classification near a project before you send bid invitations.
- Trade recruiting: list the master and journeyman electricians or plumbers who opted in to the registry in a county.
- List refresh: re-run monthly to pick up newly opted-in businesses and updated expiry dates.

### Why this actor

- Phone on 4,713 and email on 4,633 of the registry's 5,032 contractor listings (counted 2026-10-06).
- Licence number, expiry and qualifier on every one of 286 Salt Lake and Utah County roofing and HVAC listings in a test run (2026-10-06), fetched from each listing's detail record.
- Straight from DOPL's Construction Business Registry, which lists only active licences whose holders asked to be found.
- Exact place filtering: a county is resolved from the US Census place table and a city is matched exactly, never as a substring.
- Pay per listing returned; the county and classification lists are free.

### Limitations & updates

Covers only licensees listed in Utah's Construction Business Registry: active licences whose holders opted in, not the full DOPL licence roll, and only listings with a Utah address. Contact details are self-provided by the licensee. County is derived from the listed city and is approximate; a city the Census table does not know (or a blank city) leaves county null. The classifications field lists the qualifiers' classifications, which DOPL notes may cover a broader scope of work than their names suggest. Licence details come from one detail request per listing; if that request fails the row ships with the list fields and says so in enrichment_status. If the registry does not answer, the run returns a free row saying so instead of an empty dataset.

### FAQ

**Is this every licensed contractor in Utah?**

No. The Construction Business Registry lists only licensees with an active licence who voluntarily opted in, and the registry says so itself. DOPL licenses far more contractors than it lists. To verify any licence, use DOPL's Online License Verification.

**How many listings are there?**

Counted on the live registry 2026-10-06: 5,032 contractors (162 of them also under Handyman), 604 electricians, 191 plumbers, 21 burglar alarm licensees and 1 elevator mechanic. Contractors by county: Salt Lake 1,579, Utah 972, Washington 494, Davis 414, Weber 370. Salt Lake County roofers alone are 63.

**How is the county worked out?**

The registry has no county field, so each listing's city is looked up in the US Census Bureau's 2020 place-to-county table for Utah. That resolved 4,930 of 5,032 contractor listings (98.0%); 69 of the rest list no city at all. Draper, Bluffdale, Park City and Santaquin straddle two counties and are assigned to the one most of the city lies in (county_method = city_primary). Treat county as approximate.

**How do I name a trade?**

By DOPL class code ('S280'), by name ('Roofing', 'General Building'), or by a word from the name ('roof' finds S280 Roofing). For electricians and plumbers use the licence level: Master, Journeyman or Apprentice. Turn on Discovery Mode to get every classification with its code, free.

**Why is the street address empty on some rows?**

Contact details in the registry are self-provided, and many licensees give only a city and ZIP. In a test run of 286 listings, 160 had a street address and 285 a ZIP. If enrichment_status is detail_unavailable, the detail record did not answer and only the list fields shipped.

**How am I charged?**

One event per listing returned ($0.002). A listing that matches two of your searches is returned and charged once. The county and classification lists, and any row explaining a source problem, are free.

### Which actor to choose

Part of the contractor-data suite — pick the one that fits your goal:

- **Arkansas Contractor Directory & Leads Scraper (ACLB)** — You need the same trade-by-county contractor list for Arkansas.
- **California Contractor Directory by Trade & County (CSLB)** — You need the same trade-by-county contractor list for California.
- **Oregon Contractor Directory & Leads Scraper (CCB)** — You need the same county-level directory for Oregon.
- **Washington Contractor Directory & Leads Scraper (L\&I)** — You need the same county-level directory for Washington.
- **Nevada Contractor Directory & Leads Scraper (NSCB)** — You need the same county-by-trade contractor list for Nevada, with phone and street address.

### 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 Utah Contractor Directory & Leads Scraper (DOPL)](https://scrapebench.dev/guides/how-to-utah-contractor-directory-leads-scraper/)
- **Use case:** [Build a Salt Lake County Roofing Contractor List](https://scrapebench.dev/use-cases/salt-lake-county-roofing-contractors/)
- **Use case:** [Build a Utah County HVAC Contractors List (with Davis County)](https://scrapebench.dev/use-cases/utah-county-hvac-contractors/)

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/utah-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 roofing contractors in Salt Lake County, Utah, from the Construction Business Registry, with their phone numbers and email addresses."

Each row also carries the DOPL licence number and expiry date, so you can verify the licence before you call. Runs started this way bill exactly like any other run.

# Changelog

This Actor's version history is a separate document: https://apify.com/scrapebench/utah-contractor-directory-leads-scraper/changelog.md

# Actor input Schema

## `listOptionsOnly` (type: `boolean`):

If true, skip the scrape and return Utah's 29 counties plus every category and classification the Construction Business Registry (CBR) searches by, with its code. Free, not charged.

## `trade` (type: `string`):

A DOPL classification by name or code, e.g. 'Roofing', 'S280', 'HVAC', 'General Building', 'B100'. A word from the name works too: 'roof' finds S280 Roofing. For electricians and plumbers use the licence level: 'Master', 'Journeyman', 'Apprentice'. Leave empty for every classification.

## `trades` (type: `array`):

Several classifications in ONE run, e.g. Roofing and HVAC. Combined with 'Trade' if you fill both. A business listed under two of them is returned (and billed) once.

## `category` (type: `string`):

Which part of the registry: 'contractor' (default), 'handyman', 'electrician', 'plumber', 'burglar alarm' or 'elevator mechanic'. Electricians and plumbers are mostly individual licensees.

## `categories` (type: `array`):

Several categories in ONE run. Combined with 'Category' if you fill both.

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

Utah county, e.g. 'Salt Lake', 'Utah County', 'washington'. The registry has no county field, so county is worked out from each listing's city (see county_method). Leave empty for all of Utah. If it is not a Utah county, the run returns the free county and classification lists instead of failing.

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

Several counties in ONE run, e.g. Salt Lake and Utah and Davis. Combined with 'County' if you fill both.

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

A Utah city or town, e.g. 'Provo', 'St. George'. Exact city match ('Salt Lake City' does not include North Salt Lake). Use instead of, or as well as, a county.

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

Several cities in ONE run. Combined with 'City' if you fill both.

## `includeDetails` (type: `boolean`):

Fetch each listing's detail record: licence number, licence type, expiry, street address, ZIP and qualifier names. One extra request per listing; turn off for a faster run with name, phone, email and city only.

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

Cap on the listings returned PER search (each classification x each county or city), not per run, so adding places can never silently truncate the later ones. Leave empty to return every match.

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

Optional. The registry answers direct requests; enable a proxy only if runs come back with a 'blocked' notice.

## Actor input object example

```json
{
  "listOptionsOnly": false,
  "trade": "Roofing",
  "trades": [
    "Roofing",
    "HVAC"
  ],
  "category": "contractor",
  "categories": [],
  "county": "Salt Lake",
  "counties": [
    "Salt Lake",
    "Utah"
  ],
  "city": "Provo",
  "cities": [],
  "includeDetails": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Each row is one registry listing: name, phone, email, city, county, address, licence and qualifier.

# 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 = {
    "trade": "Roofing",
    "trades": [
        "Roofing",
        "HVAC"
    ],
    "county": "Salt Lake",
    "counties": [
        "Salt Lake",
        "Utah"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapebench/utah-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 = {
    "trade": "Roofing",
    "trades": [
        "Roofing",
        "HVAC",
    ],
    "county": "Salt Lake",
    "counties": [
        "Salt Lake",
        "Utah",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("scrapebench/utah-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 '{
  "trade": "Roofing",
  "trades": [
    "Roofing",
    "HVAC"
  ],
  "county": "Salt Lake",
  "counties": [
    "Salt Lake",
    "Utah"
  ]
}' |
apify call scrapebench/utah-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/utah-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/WiNn52ruywQvekZsO/builds/XrztKZPlY2luTknKb/openapi.json
