# US Contractor License Scraper — Multi-State Trade Registry (`avant_technologies/contractor-license-scraper`) Actor

Scrape contractor and trade license registries across US states: license number, status, business name, owner, expiry, contact. Official government data.

- **URL**: https://apify.com/avant\_technologies/contractor-license-scraper.md
- **Developed by:** [Rushi Patel](https://apify.com/avant_technologies) (community)
- **Categories:** Lead generation, Automation, Other
- **Stats:** 2 total users, 0 monthly users, 93.8% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 results

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

## US Contractor License Scraper — Multi-State Trade Registry

**Scrape US contractor & trade license registries from official government open data — normalized into one clean schema, across multiple states, in a single run.**

Contractor license lookup at scale: pull HVAC, plumbing, electrical, roofing, and general contractor license data from official state registries and get one dataset record per license, plus a `run_summary` (saved to the run's key-value store) with per-state status and capability flags. No login. No CAPTCHA. Just official government open data, normalized.

***

### Sample output (real, live data)

Genuine records pulled from live sources:

| State | License # | Type | Status | Business | Owner/Qualifier | City | Expires |
|---|---|---|---|---|---|---|---|
| CA | 1000004 | general\_contractor | active | SOUTH COAST CONSTRUCTION & DEVELOPMENT | — | LOS ANGELES | 2028-04-30 |
| WA | 04CONCL862CR | general\_contractor | active | 04 CONSTRUCTION LLC | Ochoa, Eric James | BEND | 2028-03-03 |
| OR | 100215 | general\_contractor | active | BRUCE HUIE | BRUCE HUIE | BROOKINGS | 2028-02-06 |
| TX | 100000 | electrical | active | JOHNSON, RUSSELL D | JOHNSON, RUSSELL D | — | 2026-08-18 |
| FL | CBC006231 | general\_contractor | active | BUILDING CONCEPTS OF TAMPA BAY, LLC | WALTERS, DENNIS D | TAMPA | 2026-08-31 |

Every record preserves the full original source fields under `raw {}`, plus `sourceUrl` and `retrievedAt` for provenance.

***

### Coverage — 8 states

Coverage breadth is the product. Every source below is official government open data — **no login, no CAPTCHA.**

| State | Trades covered | Approx. dataset size | Status filter | Date filter | Phone | Address | Bond/Insurance | Source |
|---|---|---|---|---|---|---|---|---|
| **CA** | general, electrical, plumbing, HVAC, roofing | ~700,000 | Yes | Yes | Yes | Street | Bond + workers-comp | CA CSLB — cslb.ca.gov |
| **WA** | general, electrical, plumbing, HVAC, roofing | ~161,000 | Yes | Yes | Yes | Street | via companion data (not joined in v1) | WA L\&I — data.wa.gov |
| **OR** | general + CCB specialties | ~56,000 | Active-only dataset | Yes | Yes | Street | Yes | OR CCB — data.oregon.gov |
| **TX** | electrical, HVAC/ACR | *(from TDLR's ~984k all-license dataset)* | Current licenses | No | No | County only | No | TX TDLR — data.texas.gov |
| **FL** | general, roofing, plumbing, HVAC, electrical | ~300,000 | Yes | Yes | No | Street | No | FL DBPR — myfloridalicense.com |
| **IL** | roofing *(IL's only state-licensed trade)* | ~33,000 roofing | Yes | Yes | No | City/ZIP | No | IL IDFPR — data.illinois.gov |
| **CO** | electrical, plumbing *(state-licensed trades)* | (from DORA's ~1.6M all-license dataset) | Yes | Yes | No | City/ZIP | No | CO DORA — data.colorado.gov |
| **VA** | general, electrical, plumbing, HVAC, roofing | contractors + tradesmen | Active only | Yes | No | Street + **email** | No | VA DPOR — dpor.virginia.gov |

**Read the fine print honestly:**

- **Some states only license certain trades.** **TX** covers **electrical + HVAC/ACR** only (not GC/roofing/plumbing — plumbers are TSBPE). **IL** state-licenses only **roofing**. **CO** licenses only **electrical + plumbing** (GC/HVAC/roofing are local). Requests for a trade a state doesn't license are skipped and noted in the run summary.
- **OR and TX** sources publish current/active licenses only (no historical status column). **CA, WA, and FL** expose status, so you can pull expired, revoked, and suspended records too.
- All five sources are official government open data.
- **FL uses a residential proxy on the Apify platform (on by default).** Florida's DBPR files sit behind Cloudflare, which blocks Apify's datacenter IPs, so FL is pulled through residential proxy automatically — the proxy is used **only** when FL is in `states` (WA/OR/TX never touch it). This needs a plan with residential proxy; without one, FL returns 0 records with a clear message telling you why. Running locally, FL works without a proxy.

***

### Input

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `states` | array of 2-letter codes | **Yes** | — | e.g. `["WA","OR"]`. Use `["all"]` for all supported states. |
| `licenseTypes` | array | No | all trades | Normalized values: `general_contractor`, `electrical`, `plumbing`, `hvac`, `roofing`. Empty = all trades. |
| `status` | enum | No | `active` | `active` or `all`. |
| `registeredSince` | ISO date `YYYY-MM-DD` | No | — | Sources that can't filter by date are noted in the run summary. |
| `city` | string | No | — | Exact match. County-only sources ignore this and note it. |
| `searchName` | string | No | — | Substring lookup by business/licensee name. |
| `maxItems` | integer | No | `1000` | **Per state** — a 4-state run can return up to 4× this. `0` = unlimited. |
| `useResidentialProxy` | boolean | No | `true` | Pull FL through Apify residential proxy (used **only** when FL is in `states`; WA/OR/TX never use it). On by default so FL works out of the box; needs a plan with residential proxy. |

***

### Output

One dataset record per license. A `run_summary` with per-state status and capability flags is saved to the run's key-value store (`RUN_SUMMARY`) — **you're only charged for license records, not the summary.**

**Per-license record fields:**

`state`, `licenseNumber`, `licenseType` (normalized), `licenseTypeRaw`, `status` (`active` | `expired` | `revoked` | `suspended` | `other`), `businessName`, `ownerOrQualifierName`, `phone`, `email`, `address`, `city`, `zip`, `issueDate`, `expiryDate`, `bondOrInsuranceInfo`, `sourceUrl`, `retrievedAt`, `raw {}` (full original source fields preserved).

**Example — Oregon record (shows bond/insurance):**

```json
{
  "state": "OR", "licenseNumber": "100215", "licenseType": "general_contractor",
  "licenseTypeRaw": "Residential General Contractor", "status": "active",
  "businessName": "BRUCE HUIE", "ownerOrQualifierName": "BRUCE HUIE",
  "phone": "5412150829", "city": "BROOKINGS", "zip": "97415",
  "issueDate": "1994-06-20", "expiryDate": "2028-02-06",
  "bondOrInsuranceInfo": "Bond: WESTERN SURETY COMPANY $25000; Insurance: SCOTTSDALE INDEMNITY CO $1000000",
  "sourceUrl": "https://data.oregon.gov/d/g77e-6bhs"
}
```

***

### Quickstart

Minimal input — scrape all active licenses in Washington:

```json
{ "states": ["WA"] }
```

Multi-state pull of general contractors and roofers, up to 5,000 per state:

```json
{ "states": ["WA","OR","TX","FL"], "licenseTypes": ["general_contractor","roofing"], "maxItems": 5000 }
```

***

### Use case: Verification / KYB

Every record ships with `sourceUrl` + `retrievedAt`, so you can trace any license back to its official government source — exactly the provenance KYB and compliance teams need. To verify a specific contractor, combine `searchName` with `state` and check the returned `status` and `expiryDate`. Ideal for high-value, low-volume contractor verification checks.

### Use case: Lead generation

Pull all active `general_contractor`, `roofing`, or `hvac` licenses in a state, complete with `phone` and `address` for outreach. State license registry data means fresh, official contact points for building trade-specific lead lists.

> **Note:** This data is dual-use. When using license data for outreach, comply with all applicable outreach and marketing laws (TCPA, CAN-SPAM, state do-not-call rules, etc.).

***

### Architecture — built for reliability

- **Per-state adapter modules** with a capability registry — each state's quirks are handled in isolation, and the run summary reports exactly what each source supports.
- **Per-state failure isolation** — a WA outage never kills a FL pull. Each state succeeds or fails on its own.
- **Provenance on every record** — `sourceUrl` + `retrievedAt` on each license so buyers can verify against the official source.

***

### Roadmap — new states added regularly

- ✅ Recently shipped: **CA** (CSLB, ~700k), **VA** (all trades + email), **IL** (roofing), **CO** (electrical + plumbing).
- Next: **AZ** (ROC, all trades), **NC** (all trades), and more, prioritized by market size.

Coverage breadth is the moat — most incumbents cover only 1–4 states, and this actor keeps expanding.

***

### Pricing

**Pay-per-result:** **$2.00 per 1,000 records** for bulk pulls.

**Optional low-volume verification lookups** can be priced per-lookup (~**$0.005/lookup**) — a good fit for KYB users doing high-value, low-volume contractor verification checks.

Coverage breadth is the moat: most incumbents cover only 1–4 states.

***

### FAQ

**Where does the data come from? Is it legal / reliable?**
Every state is sourced from official government open data portals (WA L\&I on data.wa.gov, OR CCB on data.oregon.gov, TX TDLR on data.texas.gov, FL DBPR on myfloridalicense.com). No login, no CAPTCHA. Every record carries `sourceUrl` and `retrievedAt` so you can verify it against the source.

**Why doesn't Texas return general contractors, roofers, or plumbers?**
Because TX TDLR only licenses **electrical** and **HVAC/ACR** trades at the state level. It does not license general contractors or roofers, and plumbers are handled by a separate agency (TSBPE). If you request an unsupported trade for TX, it's skipped and noted in the run summary rather than silently dropped.

**Can I pull expired, revoked, or suspended licenses?**
It depends on the state. **WA and FL** expose license status, so you can set `status: "all"` and get expired/revoked/suspended records. **OR and TX** sources publish only current/active licenses (no historical status column), so status filtering there is limited by the source.

**Why did my `registeredSince` or `city` filter get ignored for some states?**
Capabilities vary by source. Some states can't filter by date (see the coverage table's Date filter column), and county-only address sources like TX can't match on `city`. When a filter can't be applied, the actor notes it in the `run_summary` instead of returning wrong results.

**How does `maxItems` work across multiple states?**
`maxItems` is **per state**. A 4-state run with `maxItems: 5000` can return up to 20,000 records. Set `maxItems: 0` for unlimited.

**What states are supported, and are more coming?**
Currently covers **CA, WA, OR, TX, FL, IL, CO, VA** — with **CA (CSLB, ~700k licenses)** the largest. Next up: **AZ, NC** and more. New states are added regularly.

***

*Keywords: contractor license lookup, contractor license scraper, trade license data, contractor verification, contractor leads, state license registry, HVAC / plumbing / electrical / roofing contractor list.*

# Actor input Schema

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

Two-letter state codes to pull. Supported: CA, WA, OR, TX, FL, IL, CO, VA. Use \["all"] for every supported state. More states added regularly.

## `licenseTypes` (type: `array`):

Filter to normalized trades. Valid values: general\_contractor, electrical, plumbing, hvac, roofing. Leave empty for all trades in each state.

## `status` (type: `string`):

active = only active licenses; all = every status the source exposes (some sources only publish active).

## `registeredSince` (type: `string`):

Only licenses issued/registered on or after this ISO date (YYYY-MM-DD). Sources that can't filter by date are noted in the run summary.

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

Optional city filter (exact match). Sources with only county-level location ignore this and note it.

## `searchName` (type: `string`):

Optional lookup by business or licensee name (substring match).

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

Maximum records to return PER state (so a 4-state run can return up to 4x this). 0 = unlimited.

## `useResidentialProxy` (type: `boolean`):

FL's DBPR files are behind Cloudflare, which blocks Apify datacenter IPs, so FL is pulled via residential proxy (small extra proxy cost, and requires a plan with residential proxy). The proxy is ONLY used when FL is in 'states' — WA/OR/TX never use it. Turn off to disable FL proxying.

## Actor input object example

```json
{
  "states": [
    "CA",
    "WA",
    "TX",
    "FL",
    "VA",
    "IL"
  ],
  "licenseTypes": [
    "general_contractor",
    "roofing"
  ],
  "status": "active",
  "maxItems": 1000,
  "useResidentialProxy": true
}
```

# Actor output Schema

## `licenses` (type: `string`):

One dataset record per license. The run summary (per-state status) is in the run's key-value store (RUN\_SUMMARY).

# 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": [
        "CA"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("avant_technologies/contractor-license-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": ["CA"] }

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

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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": [
    "CA"
  ]
}' |
apify call avant_technologies/contractor-license-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=avant_technologies/contractor-license-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/V9PmbAUimRUa1QWfm/builds/TUeHjY07XzSczvAQw/openapi.json
