# Arizona Contractor Leads - ROC Licenses with Phone & Address (`scrapersdelight/arizona-roc-contractor-leads-scraper`) Actor

$3 per 1,000 licences, no start fee. Every current Arizona ROC contractor licence from the registrar's own daily list, with phone, street address, class, qualifying party, personnel, issue and expiry dates, bonds and complaints. Filter by trade, city, ZIP, new licences or expiry.

- **URL**: https://apify.com/scrapersdelight/arizona-roc-contractor-leads-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Lead generation, Business, Automation
- **Stats:** 2 total users, 1 monthly users, 90.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 per licence returneds

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

## 🏗️ Arizona Contractor Leads - ROC Licenses with Phone & Address

Every currently licensed contractor in Arizona, from the **Arizona Registrar of Contractors' (ROC) own daily list of active licences** (58,409 on 2026-09-29), with the **business phone number ROC publishes (99.7% of rows)**, the street address, classification, qualifying party, owners and officers, issue and expiry dates, surety bonds and complaint record.

Filter by trade, city, ZIP, licence scope, newly issued licences or licences about to expire, or verify specific contractors by licence number or name. No login, no API key, no browser.

### What does this actor do?

ROC publishes two things. Its **posting list** is a daily CSV of every current active contractor licence, with street address, class and dates but no phone. Its **contractor search** shows the phone, owners, bonds and complaints, but one search at a time. This actor filters the posting list the way you ask, then looks each licence up in ROC's contractor search and joins the phone, personnel, bond and complaint record onto the row by exact licence number.

**Who uses it**

- **Building-product distributors and manufacturer reps** building a territory list of licensed plumbers, electricians, HVAC, roofing or pool contractors, with a phone number for each.
- **Trade insurers and surety brokers** working new licences (issued in the last N days) and licences coming up for renewal, with the current surety company and bond on every row.
- **Contractor-marketing agencies** prospecting Arizona contractors by city, ZIP and trade.
- **Platforms and general contractors** verifying a list of subcontractors' licence numbers, status and expiry.

### ✅ Why the list is complete

ROC's contractor search is not a reliable way to pull a list: it silently stops at roughly **1,000 businesses per search**, and its city filter is applied *after* that limit. Measured on the live search on 2026-09-29:

- A statewide search for class **R-62 Minor Home Improvements** returned **953** businesses. The same class split into licence-number ranges holds **2,275** businesses (2,539 licences).
- **R-62 in Phoenix**: the search returned **122** licences; the complete set holds **313**.
- A city-only search returns **227-325 businesses whatever the city**: Flagstaff 227, Phoenix 253. This actor returns **721 active licences in Flagstaff**.

This actor filters the **whole posting list**, so a filter returns every matching active licence. The contractor search is only used to look up licences it already has, by exact licence number, so its limit never truncates your list. Every run records in `RUN_SUMMARY` how every licence was matched.

### 📋 What you get per licence

One row per ROC licence (a business with three licences is three rows, each listing the other two in `otherLicenses`). Fill measured on a real run: every active licence in **Flagstaff (721 rows)**.

| Field | What it is | Fill (Flagstaff, 721) |
|---|---|---|
| `licenseNumber` | ROC licence number, e.g. `ROC 366555` | 100% |
| `businessName` | Licensed (legal) business name | 100% |
| `dbaName` | Doing-business-as name, when ROC lists one | 21.8% |
| `phone` | Business phone ROC publishes, formatted `(928) 856-1204` | **99.7%** |
| `phoneE164` | The same number as `+19288561204` for dialers and CRMs | 99.7% |
| `phoneAsPublished` | The phone text exactly as ROC shows it | 99.7% |
| `streetAddress` | Street address of record, from ROC's posting list | **100%** |
| `city`, `state`, `zip` | City, state and ZIP of the address of record | 100% |
| `classificationCode` | ROC class code: `CR-37`, `KB-2`, `B`, `R-62`... | 100% |
| `classification` | Class as ROC names it: `CR-34 Painting and Wall Covering` | 100% |
| `classType` | ROC class type: General/Specialty x Commercial/Residential/Dual | 100% |
| `licenseScope` | `Commercial`, `Residential` or `Dual` | 100% |
| `classificationScopes` | For dual classes, the commercial and residential classes they cover (CR-37 = C-37 Plumbing + R-37R Plumbing) | 60.1% (dual classes) |
| `classificationDescription` | ROC's description of the work the class allows | 100% |
| `status` | Licence status (`Active` for every list row) | 100% |
| `accountStatus` | ROC account status action when set (`Non-Renewal`, `Lack of Bond`...) | 0.1% |
| `restrictedScope` | The restriction on restricted classes (CR-5, KE, KO) | 3.5% |
| `qualifyingParty` | The person responsible for the licence | 98.1% |
| `qualifyingPartyExempt` | `true` when ROC marks the licence QP Exempt | 100% |
| `personnel` | Everyone on the licence account: `[{name, title}]` (owner, member, officer, qualifying party) | 100% |
| `issuedDate` | Date first issued | 100% |
| `expirationDate` | Date the licence expires (renewed through) | 100% |
| `daysToExpiry` | Days from the run date to `expirationDate` | 100% |
| `statusChangedDate` | Date of the last status change | 84.5% |
| `entityType` | LLC, Corporation, Sole Proprietorship, Partnership... | 100% |
| `bondCompany` | Surety company of the active bond | 99.2% |
| `bondNumber` | Bond number | 99.9% |
| `bondAmount` | Bond amount in USD (null when ROC records $0, see limits) | 69.5% |
| `bondType`, `bondStatus`, `bondEffectiveDate` | Bond type (Surety, Cash, CD), status, effective date | 97.9-99.9% |
| `bonds` | Every bond on the licence, with cancellation date and payouts | 99.9% |
| `openComplaints`, `disciplinedComplaints`, `resolvedComplaints`, `complaintCount` | ROC's complaint counts | 100% |
| `complaints` | The complaint records ROC shows: case number, type, allegation, outcome, status, closed date, jobsite city | 3.5% (licences with complaints) |
| `otherLicenses` | The business's other ROC licences and their status | 34.1% |
| `licenseId`, `accountId`, `profileUrl` | ROC's record ids and the licence's page on ROC's contractor search | 100% |
| `inContractorSearch`, `detailsFetched` | Whether the search join and the licence detail succeeded for this row | 100% |
| `source`, `searchQuery`, `postingListDate`, `scrapedAt` | Where the row came from, and when | 100% |

On the **1,000 newest plumbing licences** (a second sample): phone 99.9%, street address 100%, bond company 99.4%, DBA 19.1%.

ROC does **not** publish an e-mail address or a website for contractors, so this actor has no e-mail or website field.

### Example row (real, from a live run on 2026-09-30)

```json
{
  "licenseNumber": "ROC 366555",
  "businessName": "HIGH COUNTRY SPECIALTY CONSTRUCTION, LLC",
  "dbaName": "High Country Construction",
  "phone": "(928) 856-1204",
  "phoneE164": "+19288561204",
  "phoneAsPublished": "(928) 856-1204",
  "streetAddress": "2220 S. FLAGSTAFF RANCH RD.",
  "city": "Flagstaff",
  "state": "AZ",
  "zip": "86001",
  "classificationCode": "CR-34",
  "classification": "CR-34 Painting and Wall Covering",
  "classType": "Specialty Dual",
  "licenseScope": "Dual",
  "classificationScopes": ["C-34 Painting and Wall Covering", "R-34 Painting and Wall Covering"],
  "classificationDescription": "CR34 PAINTING AND WALL COVERING This classification allows the scopes of work permitted by the commercial C-34 Painting and Wall Covering and the residential R-34 Painting and Wall Covering licenses.",
  "status": "Active",
  "accountStatus": null,
  "restrictedScope": null,
  "qualifyingParty": "JUSTIN HANSON",
  "qualifyingPartyExempt": false,
  "personnel": [
    { "name": "JUSTIN HANSON", "title": "Qualifying Party" },
    { "name": "JUSTIN HANSON", "title": "Owner" }
  ],
  "issuedDate": "2026-06-29",
  "expirationDate": "2028-06-30",
  "daysToExpiry": 639,
  "statusChangedDate": "2026-06-29",
  "entityType": "LLC",
  "bondCompany": "Platte River Insurance Company",
  "bondNumber": "SB276754",
  "bondAmount": 6750,
  "bondType": "Surety",
  "bondStatus": "Active",
  "bondEffectiveDate": "2026-06-19",
  "bonds": [
    { "bondNumber": "SB276754", "type": "Surety", "status": "Active", "amount": 6750, "company": "Platte River Insurance Company", "effectiveDate": "2026-06-19", "cancellationDate": null, "totalPayout": 0 }
  ],
  "openComplaints": 0,
  "disciplinedComplaints": 0,
  "resolvedComplaints": 0,
  "complaintCount": 0,
  "complaints": [],
  "otherLicenses": [],
  "licenseId": "a0ocs00000ObImXAAV",
  "accountId": "001cs00001tz9flAAA",
  "profileUrl": "https://azroc.my.site.com/AZRoc/s/contractor-search?licenseId=a0ocs00000ObImXAAV",
  "inContractorSearch": true,
  "detailsFetched": true,
  "source": "posting-list",
  "searchQuery": null,
  "postingListDate": "2026-09-29",
  "scrapedAt": "2026-09-30T00:40:25.798Z"
}
```

### 🔍 Input

Every filter is optional. With none, you get the newest active licences statewide. Rows always come **newest licence first** (ROC issues licence numbers in sequence), so a small `maxResults` gives you the most recently licensed contractors.

| Input | What it does | Example |
|---|---|---|
| `classifications` | ROC class codes, full class names, or trade words matched inside ROC's class names. `"plumbing"` catches C-37, CR-37, R-37 and the solar plumbing classes; `"roofing"` catches C-42, CR-42, R-42. Dual classes have their own codes (CR-37 is dual plumbing), so list both if you want both. | `["CR-37", "C-37"]`, `["roofing"]` |
| `cities` | City of the address of record, as ROC writes it (case does not matter). | `["Phoenix", "Mesa"]` |
| `zipCodes` | Five-digit ZIPs or prefixes. `"852"` is every ZIP starting 852. | `["85255", "857"]` |
| `licenseScopes` | Keep only `Commercial`, `Residential` or `Dual` licences. | `["Residential"]` |
| `issuedWithinDays` | Only licences first issued in the last N days: newly licensed contractors. | `30` |
| `expiringWithinDays` | Only licences that expire in the next N days: the renewal list. | `60` |
| `licenseNumbers` | Look up specific licences, any status (active, expired, suspended, revoked, cancelled). `ROC 349004`, `349004` and `49004` all work. | `["ROC 349004"]` |
| `searchNames` | Search ROC's contractor search by business, DBA or person name (first name then last name). Every licence of every matching business is returned. | `["Alpha Plumbing"]` |
| `includeInactiveLicenses` | Name searches return only active licences unless this is on. | `false` |
| `onlyWithPhone` | Skip licences without a valid phone number (not delivered, not charged). | `false` |
| `includeDetails` | Entity type, status-change date, account status, bonds, complaints and ROC's class description. No extra charge. | `true` |
| `maxResults` | Stop after this many licences. `0` = every match. Default 100. | `500` |
| `proxyConfiguration` | Not needed: the actor manages its own connections. A proxy set here is tried first. | |

**Examples**

Every licensed plumbing contractor (commercial, residential and dual) in Phoenix:

```json
{ "classifications": ["plumbing"], "cities": ["Phoenix"], "maxResults": 0 }
```

Contractors licensed in the last 7 days, statewide, with a phone number:

```json
{ "issuedWithinDays": 7, "onlyWithPhone": true, "maxResults": 0 }
```

Residential roofing licences in the Tucson ZIPs that expire in the next 60 days:

```json
{ "classifications": ["roofing"], "zipCodes": ["857"], "licenseScopes": ["Residential"], "expiringWithinDays": 60, "maxResults": 0 }
```

Verify a list of subcontractors:

```json
{ "licenseNumbers": ["ROC 349004", "211279", "ROC 002386"] }
```

### 💵 Pricing

**$3 per 1,000 licences** ($0.003 per licence delivered), pay per result. No start fee, and nothing else is charged:

- Licences your filters exclude are never delivered and never charged.
- A licence number ROC does not know, or a licence the search could not be reached for, is listed in `RUN_SUMMARY` and not charged.
- `includeDetails` (bonds, complaints, entity type) costs nothing extra.

A run with no input delivers the 100 newest licences for $0.09. All 3,448 active plumbing licences in Arizona (every class with "plumbing" in its name, 2026-09-29) cost $3.10; the 443 in Phoenix cost $0.40.

### ⏱️ Speed

Measured on the platform: the 20 newest CR-37 plumbing contractors in Mesa in about 30 seconds; the 1,000 newest plumbing licences statewide in 79 seconds; every active licence in Flagstaff (721, which means reading the whole 12 MB list) in about 6 minutes. City-only runs over a large city take longest, because every licence is looked up in ROC's search, and ROC answers each look-up in 2-3 seconds. A run over every active licence in the state is long: raise the run timeout for it. A run always stops cleanly before its timeout and keeps (and charges) only what it delivered, and a run the platform restarts resumes without delivering anything twice.

### 🧾 Run summary

Every run writes a `RUN_SUMMARY` record to its key-value store:

- the posting-list file used, its date, the record count ROC declares in it, and how much of it was read (the file is read newest-first and the run stops as soon as it has your rows)
- how many licences each filter excluded
- how every licence was matched in ROC's search: found, not shown by the search, or unreachable (never charged)
- licence numbers not found, and name searches that hit ROC's ~1,000-business search limit
- delivered and charged counts, and the field fill of the rows you received

### ⚠️ Data limits

- **Active licences only, in list mode.** ROC's posting list holds current active licences. Expired, suspended, revoked and cancelled licences come back from `licenseNumbers` and `searchNames` lookups, without a street address (ROC publishes it only on the posting list).
- **No e-mail and no website**: ROC does not publish them.
- **Bond amounts**: ROC records many older bonds with a $0 amount (30% of the active licences in Flagstaff, mostly issued 2001-2017). Those ship as `null`, never as 0. The surety company, bond number and status are still there.
- **Complaints** are what ROC's contractor search displays for the licence.
- **City** is the city of the address of record, not where the contractor works. 16.1% of active licences (9,375 of 58,409 on 2026-09-29) have an address outside Arizona: California 2,677, Texas 937, Utah 665, Nevada 565.
- **Name searches** go through ROC's own search and stop at about 1,000 businesses per name. The run summary says when a name hit that limit. Use `classifications` and `cities` for lists.
- **Freshness**: ROC regenerates the posting list during the day. Each row carries `postingListDate`.

### ❓ FAQ

**Is this every licensed contractor in Arizona?**
Every contractor licence ROC lists as currently active on the day of the run: 58,409 on 2026-09-29.

**How is the phone number matched to the licence?**
By exact licence number. The actor looks the licence up in ROC's contractor search and takes the phone from the business that holds that licence number. It never guesses a phone from a name.

**Why are there two rows for one business?**
Each row is a licence. A business with a dual plumbing licence and a general contractor licence has two rows, each listing the other in `otherLicenses`.

**What does `inContractorSearch: false` mean?**
The licence is on ROC's posting list but ROC's contractor search does not show it, so it has no phone or personnel. It happened on 0 of the 721 Flagstaff licences.

**Can I get only newly licensed contractors every week?**
Yes: schedule a run with `issuedWithinDays: 7`. It reads only the newest part of the list, so it is fast and cheap.

### Source

Arizona Registrar of Contractors: the posting list at `roc.az.gov/posting-list` and the contractor search at `azroc.my.site.com/AZRoc/s/contractor-search`.

# Actor input Schema

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

ROC classification codes (CR-37, KB-2, B, R-62, C-11...) or trade words matched inside ROC's class names ("plumbing" catches C-37, CR-37, R-37 and the solar plumbing classes; "roofing" catches C-42, CR-42, R-42). A full label such as "CR-37 Plumbing" works too. Dual classes have their own codes: CR-37 is dual plumbing, so add it next to C-37 if you want both. Leave empty for every class.

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

City of the licensee's address of record, as ROC writes it (case does not matter): Phoenix, Tucson, Mesa, Scottsdale, Chandler, Gilbert, Glendale, Peoria... Licensees based outside Arizona keep their own city. Leave empty for every city.

## `zipCodes` (type: `array`):

Five-digit ZIPs or prefixes: "85255" is one ZIP, "852" is every ZIP starting 852 (the Phoenix East Valley). Leave empty for every ZIP.

## `licenseScopes` (type: `array`):

Keep only Commercial, Residential or Dual licences (ROC's class type). Leave empty for all three.

## `issuedWithinDays` (type: `integer`):

Keep only licences first issued in the last N days: newly licensed contractors, the buyers of bonds, insurance, supplies and software. ROC's new-licence list held 4,084 licences issued 2026-03-02 to 2026-09-28, about 20 a day. Leave empty for any issue date.

## `expiringWithinDays` (type: `integer`):

Keep only licences that expire between today and N days from now: the renewal list for bond, insurance and continuing-education sellers. Leave empty for any expiry date.

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

ROC licence numbers, with or without the ROC prefix or leading zeros: "ROC 349004", "349004". A number ROC does not know is listed in the run summary and never charged.

## `searchNames` (type: `array`):

Business names, DBA names or people (first name then last name, as ROC asks), searched in ROC's own contractor search. Every licence of every matching business is returned. ROC's search stops at about 1,000 businesses per name; the run summary says when a name hit that limit.

## `includeInactiveLicenses` (type: `boolean`):

Name searches return only Active licences unless this is on. Licence-number lookups always return the licence you asked for, whatever its status.

## `onlyWithPhone` (type: `boolean`):

Skip licences with no valid phone number. Skipped licences are not delivered and not charged.

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

Adds entity type, status-change date, account status, every bond (surety, number, amount, status), complaint counts and complaint records, and ROC's description of the classification. No extra charge.

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

Stop after this many licences, newest licence first. 0 = every match.

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

Both ROC sites answer direct connections, which is the default. A proxy set here is used only if a direct request fails.

## Actor input object example

```json
{
  "classifications": [
    "CR-37"
  ],
  "cities": [
    "Mesa"
  ],
  "includeInactiveLicenses": false,
  "onlyWithPhone": false,
  "includeDetails": true,
  "maxResults": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per ROC licence: business and DBA name, phone, street address, classification and scope, qualifying party, personnel, issue and expiry dates, entity type, bonds and complaints.

## `runSummary` (type: `string`):

RUN_SUMMARY: the posting-list file used and its declared vs parsed record count, how many licences each filter excluded, how the phone join resolved every licence (found / not in search / unreachable), lookups not found, delivered and charged counts, and the field fill of the rows you received.

# 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 = {
    "classifications": [
        "CR-37"
    ],
    "cities": [
        "Mesa"
    ],
    "maxResults": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/arizona-roc-contractor-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 = {
    "classifications": ["CR-37"],
    "cities": ["Mesa"],
    "maxResults": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/arizona-roc-contractor-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 '{
  "classifications": [
    "CR-37"
  ],
  "cities": [
    "Mesa"
  ],
  "maxResults": 20
}' |
apify call scrapersdelight/arizona-roc-contractor-leads-scraper --silent --output-dataset

```

## MCP server setup

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