# Arizona Contractor Directory (AZ ROC) – New Licenses + Phone (`deadwood_data_solutions/arizona-contractor-directory-leads-scraper`) Actor

Licensed Arizona contractors from the AZ Registrar of Contractors (ROC) public registry with business phone, owner, license class, status and issue date. Dedup means scheduled runs return only new licenses. For insurance agents, lenders and B2B sellers. Free CRM sync.

- **URL**: https://apify.com/deadwood\_data\_solutions/arizona-contractor-directory-leads-scraper.md
- **Developed by:** [K O](https://apify.com/deadwood_data_solutions) (community)
- **Categories:** Lead generation, Integrations
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 contractor leads

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

## New Arizona Contractor Leads – Licensed, with Phone & Owner

A deduplicated lead list of **licensed Arizona contractors** straight from the **AZ Registrar of Contractors (ROC)** public registry. Each record has the legal business name, **business phone (~90% fill)**, the **owner / qualifying party**, license number, license class, status, and **issue date** — the fields a salesperson actually calls off. With dedup on, a **scheduled run only returns licenses new since your last run** — a fresh AZ contractor lead feed.

### Pain points

- ROC's own license lookup is a Salesforce portal built for one-off searches: no bulk export, no CSV, no way to pull "every active contractor in Mesa" in one shot.
- Results are split across a search roster and a separate per-license detail page — the issue date, entity type and renewal date you actually need aren't on the same screen as the phone number.
- Generic Arizona business lists mix in every business type. A licensed ROC contractor is specifically a business the state requires to carry insurance — that's the high-intent signal, and a generic list can't isolate it.
- Re-scraping the same roster every week to find who's new means diffing license numbers yourself, or paying for the same contractor's contact info twice.

### What we solve

- **Business phone published right in the roster** — ROC prints it in the licensee record itself, not guessed or appended from a third-party source. ~87–100% fill across sampled searches.
- **Owner / qualifying party on every record**, resolved from the license's QP field or account contact list — the name a caller actually asks for.
- **Issue date, entity type and renewal date** pulled from each license's detail page and merged onto the roster record, so one dataset item has everything.
- **Dedup by license number across runs.** Turn on `onlyNewSinceLastRun` and a scheduled run bills only for licenses it hasn't returned before — not the whole registry, every time.
- **Free discovery before you spend anything.** Set `listCitiesOnly: true` to get the exact spelling of every valid Arizona city/town for the `city` filter, free — it returns the reference list and stops before the registry sweep starts, so a city typo can't silently return zero results on a billed run.

### Who uses it

**Insurance agents** selling workers'-comp, general liability and bonds to contractors (a licensed AZ contractor is a legally-required-coverage buyer — high intent), **lenders and equipment financiers**, materials and SaaS vendors, and anyone doing B2B outreach to Arizona's construction trades.

### Input

| Field | Description |
|---|---|
| `searchTerms` | Business-name keywords to search. Empty = sweep A–Z for broad statewide coverage. |
| `classification` | Optional ROC class code filter (e.g. `B` general commercial, `CR-11` electrical). |
| `city` | Optional city filter (e.g. `Phoenix`, `Tucson`, `Mesa`). Empty = statewide. |
| `activeOnly` | Keep only Active licenses (exclude Suspended / Cancelled / Non-Renewal). |
| `enrichDetail` | Fetch per-license detail for issue date, entity type and renewal date (on by default). |
| `onlyNewSinceLastRun` | Recommended for schedules — skips licenses already returned by a previous run. |
| `maxItems` | Stop after this many normalized records. |
| `listCitiesOnly` | Discovery mode — returns the reference list of Arizona cities/towns for the `city` filter and stops. Free, nothing charged. |

### Output

| Field | Description |
|---|---|
| `licenseNumber` | ROC license number — the stable id deduped across runs. |
| `legalName`, `dbaName` | Legal business name. ROC's public registry does not expose a DBA/trade name, so `dbaName` is always `null`. |
| `phone` | Business phone, formatted, as published in the ROC roster. |
| `email` | Always `null` — ROC's public registry does not publish licensee email. |
| `owner` | Owner / qualifying party name — the caller's actual target. |
| `entityType` | LLC, Corporation, Sole Proprietor, etc. |
| `city`, `state`, `zip` | Site location. ROC does not publish street address, only city/state/ZIP. |
| `licenseClass`, `licenseType` | ROC class code (e.g. `CR-10`) and its human-readable trade description. |
| `status`, `accountStatus` | License status and account status as published by ROC. |
| `issuedDate`, `renewalDate`, `statusChangedDate` | Issue date, renewal-due date, and last status-change date. |
| `rocUrl` | Link back to the ROC contractor search portal. |
| `source` | Always `"AZ ROC"`. |

One real record, pulled from an actual run — not a placeholder:

```json
{
  "licenseNumber": "ROC 333282",
  "legalName": "Grey Wolf Drywall LLC",
  "dbaName": null,
  "phone": "(602) 317-1895",
  "email": null,
  "owner": "Alfonso Lopez",
  "entityType": "LLC",
  "city": "Waddell",
  "state": "AZ",
  "zip": "85355",
  "licenseClass": "CR-10",
  "licenseType": "Drywall",
  "status": "Active",
  "accountStatus": null,
  "issuedDate": "2021-05-07",
  "renewalDate": "2027-05-31",
  "statusChangedDate": "2026-01-20",
  "rocUrl": "https://azroc.my.site.com/AZRoc/s/contractor-search",
  "source": "AZ ROC"
}
```

### Sync to your CRM, Notion, HubSpot, Airtable or Supabase (optional)

Turn on `syncEnabled` to also send each new contractor lead straight to a
connected app — free, with no extra charge. Connect the app under
**Integrations** in Apify Console, pick it as `syncDestination`, and set
`syncWriteTool` to the name of the tool that creates one record there (run the
[dataset-sync-connector](https://apify.com/deadwood_data_solutions/dataset-sync-connector)
Actor in `list-tools` mode against the same connector if you don't know the
name). A sync failure is logged as a warning and never blocks the dataset —
your leads always land here first regardless of what the destination does.

### Pricing (Pay-Per-Event)

- **`query`** — charged once per run for the registry sweep.
- **`contractor-record`** — charged per normalized contractor lead pushed. This is the primary event.
- **`detail-enriched`** — charged in addition, only for leads where the per-license detail record (issue date, entity type, renewal date) was fetched. Controlled by `enrichDetail` (on by default); turn it off to skip this charge and get roster-only fields.
- `apify-actor-start` (Apify-managed) — covers baseline compute per run.
- **You are billed once per contractor, per run — never twice.** Results are deduplicated by license number before they're pushed, so a contractor matching more than one search term still appears once and is charged once.
- Syncing records to a connected app (below) is free and never a billable event.

A daily run filtered to a class or city returns just the new licenses for pennies; a first statewide backfill is a larger one-time run you cap with `maxItems`.

### Source & reliability

Data comes from the public **AZ Registrar of Contractors** licensee registry (`azroc.my.site.com`). No API key, no login, no captcha, no proxy. Phone and owner are published by ROC in the licensee record; issue/renewal dates come from each license's detail record. Run `npm test` for the offline self-test covering address parsing, class-code splitting, phone formatting and owner resolution.

**Data scope:** only public ROC registry fields ship. ROC does not publish licensee **email** or **street address** (city/state/ZIP only), so those are intentionally left blank rather than guessed.

### FAQ

**How am I charged?**

One `query` event per run, plus one `contractor-record` event per normalized license pushed to the dataset. If `enrichDetail` is on (the default) and the per-license detail record was fetched for that lead, a `detail-enriched` event is charged alongside it. Filtering by `city` or `classification` narrows what's fetched — it doesn't create extra charges, since each license appears once per run regardless of which filters matched it.

**Where does the data come from?**

The public **AZ Registrar of Contractors** licensee registry (`azroc.my.site.com`) — no API key, login, or captcha. See "Source & reliability" above for exactly which fields ROC publishes.

**Is the phone always present?**

ROC publishes the business phone directly in the licensee roster, so fill runs high (~87–100% across sampled searches). It is the licensed business's own contact number, not a proximity guess.

**Do I get email?**

No — the AZ ROC public registry does not publish licensee email, so the `email` field is always null. Everything shipped is genuine public-registry data.

**How "new" are these?**

Turn on `onlyNewSinceLastRun` and schedule the actor; each run returns only licenses not seen before, so you get newly-appearing AZ contractor licenses over time.

***

*SEO keywords: Arizona contractor leads, AZ ROC data, licensed contractor list, contractor phone list, Arizona construction leads, contractor insurance leads, workers comp leads Arizona, ROC license lookup, contractor sales leads*

# Actor input Schema

## `searchTerms` (type: `array`):

Name keywords to search the ROC registry (e.g. company words, or leave empty to sweep A–Z for broad coverage). Each ROC search returns up to ~1000 matches; results are deduplicated by license number.

## `classification` (type: `string`):

Optional ROC classification code to filter by (e.g. 'B' general commercial, 'CR-11' electrical). Leave blank for all classes.

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

Optional city to filter by (e.g. Phoenix, Tucson, Mesa). Leave blank for statewide.

## `activeOnly` (type: `boolean`):

Keep only licenses with an Active status (exclude Suspended, Cancelled, Non-Renewal, etc.).

## `enrichDetail` (type: `boolean`):

Fetch the per-license detail record to add issue date, entity type and renewal date. Turn off for a faster, roster-only run (name, phone, class, status).

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

Stop after this many normalized records have been pushed.

## `onlyNewSinceLastRun` (type: `boolean`):

Recommended for scheduled runs. Uses persisted state to skip licenses already returned earlier, so a recurring schedule only charges for newly-appearing contractor licenses.

## `listCitiesOnly` (type: `boolean`):

Return the reference list of Arizona cities/towns you can pass to the 'City filter' input, then stop. No registry sweep runs and nothing is charged — use this first to get the `city` spelling exactly right before spending on a real run.

## `syncEnabled` (type: `boolean`):

Send each new record to a connected destination (Notion, HubSpot, Airtable, Supabase, or any Apify MCP connector) in addition to the dataset. Free — this never triggers a billable event. A sync failure never blocks or fails the run.

## `syncDestination` (type: `string`):

Connect the app under Integrations in Apify Console first, then pick it here. Required only if 'Sync new records' is on.

## `syncWriteTool` (type: `string`):

Name of the destination's MCP tool that creates one record, e.g. 'create\_page' for Notion, 'insert' for Supabase. Run the dataset-sync-connector Actor in 'list-tools' mode against the same destination if you don't know it.

## `syncFieldMap` (type: `object`):

Maps destination argument names to this Actor's output field names, e.g. {"title": "legalName", "phone": "phone"}. Leave empty to pass each record through unchanged.

## `syncExtraArgs` (type: `object`):

Fixed arguments merged into every sync write call, e.g. {"database\_id": "abc123"} for Notion.

## Actor input object example

```json
{
  "searchTerms": [],
  "activeOnly": false,
  "enrichDetail": true,
  "maxItems": 300,
  "onlyNewSinceLastRun": true,
  "listCitiesOnly": false,
  "syncEnabled": false,
  "syncFieldMap": {},
  "syncExtraArgs": {}
}
```

# Actor output Schema

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

All normalized records from this run as JSON.

## `resultsCsv` (type: `string`):

All normalized records from this run as CSV.

# 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 = {
    "searchTerms": [],
    "syncFieldMap": {},
    "syncExtraArgs": {}
};

// Run the Actor and wait for it to finish
const run = await client.actor("deadwood_data_solutions/arizona-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 = {
    "searchTerms": [],
    "syncFieldMap": {},
    "syncExtraArgs": {},
}

# Run the Actor and wait for it to finish
run = client.actor("deadwood_data_solutions/arizona-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 '{
  "searchTerms": [],
  "syncFieldMap": {},
  "syncExtraArgs": {}
}' |
apify call deadwood_data_solutions/arizona-contractor-directory-leads-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,deadwood_data_solutions/arizona-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/veCL9EeUWpB6UroIp/builds/fZgdyqqoQPTsZARRU/openapi.json
