# US Contractor Licenses - 9 State Boards (`j0401/us-contractor-licenses`) Actor

Contractor and trade licences from nine state boards: California CSLB, Washington L\&I, New Jersey DCA, Florida DBPR, Minnesota DLI, Ohio OCILB, Oregon CCB, Virginia DPOR and Texas TDLR. One schema; filter by name, licence number, licence type, classification, status, city, county or ZIP.

- **URL**: https://apify.com/j0401/us-contractor-licenses.md
- **Developed by:** [Wenhao Yang](https://apify.com/j0401) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.03 / 1,000 us contractor licence records

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?

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

## US Contractor Licenses - 9 State Boards, One Schema

Contractor and trade licences from nine state boards. Check a contractor, look up a licence number, screen a city or county, or roll a state's licences up by type and classification.

### Low cost

**From $0.00005 per record, down to $0.00003 at Gold.**

### What you get

Each record carries:

- `state`, `source` - the board the row came from
- `licenseNumber`, `licenseType`, `classification`
- `holderName`, `businessName`
- `status` (normalised `active` / `inactive` / `unknown`), `statusRaw` (the board's own word)
- `issueDate`, `expiryDate`
- `address`, `city`, `county`, `zip`, `phone`
- `sourceUrl`, `sourceUpdatedAt`

### States

| `state` | Board | Rows | What it covers |
|---|---|---:|---|
| `CA` (default) | CSLB | 245,545 | Every licensed contractor, with the board class (A / B / C-xx) |
| `WA` | L\&I | 161,794 | Construction contractors, active and historical |
| `NJ` | DCA | 96,776 | Electrical, home improvement, plumbing, HVACR and home-inspection licences |
| `FL` | DBPR | 135,213 | Construction and trade licences |
| `MN` | DLI | 259,753 | Construction, electrical, plumbing, boiler and related trades |
| `OH` | OCILB | 12,498 | Contractor licences |
| `OR` | CCB | 56,580 | Every CCB endorsement |
| `VA` | DPOR | 84,610 | Class A/B/C contractors and tradesmen only |
| `TX` | TDLR | 38,102 | Contractor licence types only |

Total corpus: **1,090,871 rows**, measured 2026-10-23.

### Classification

California puts the trade class in `classification`: `A` (general engineering), `B` (general building) and `C-xx` specialty classes such as `C-10` electrical, `C-20` warm-air/HVAC, `C-36` plumbing, `C-39` roofing. The filter matches a **whole class token** and ignores hyphens, so `C10` and `C-10` are the same and `C10` does not match `C100`. A row can carry several classes (`"B, C10"`), and any of them matches.

Other states use descriptive classifications; the same filter then matches a substring of the classification/specialty text.

In `mode=aggregate`, `maxResults` caps the number of **group rows** (default 50 - raise it for the long tail).

### Examples

```json
{ "state": "CA", "classification": "C10", "maxResults": 100 }
```

```json
{ "state": "CA", "name": "SMITH", "city": "LOS ANGELES", "maxResults": 100 }
```

```json
{ "state": "WA", "licenseType": "CONSTRUCTION CONTRACTOR", "maxResults": 100 }
```

```json
{ "state": "NJ", "licenseType": "Electrical Contractors", "status": "active", "maxResults": 100 }
```

```json
{ "state": "FL", "licenseNumber": "CBC006231" }
```

```json
{ "state": "TX", "licenseType": "A/C Contractor", "maxResults": 100 }
```

```json
{ "state": "CA", "mode": "aggregate", "groupBy": "classification" }
```

### Which fields each state fills

Percent of rows with a non-empty value, measured on this corpus:

| state | holderName | businessName | classification | status | issueDate | expiryDate | address | city | county | zip | phone |
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
| CA | - | 100 | 100 | 100 | 100 | 100 | 100 | 100 | 97 | 100 | 100 |
| WA | 100 | 100 | 100 | 100 | 100 | 100 | 100 | 100 | - | 100 | 100 |
| NJ | 54 | 46 | 100 | 100 | 100 | 96 | 100 | 100 | 100 | 100 | - |
| FL | 100 | 85 | 15 | 100 | 99 | 86 | 98 | 98 | 96 | 98 | - |
| MN | 100 | 4 | 100 | 100 | 100 | 100 | 100 | 100 | - | 100 | 88 |
| OH | 100 | 98 | - | 100 | 100 | 100 | 98 | 98 | 97 | 98 | 65 |
| OR | - | 100 | 100 | - | 100 | 100 | 100 | 100 | 99 | 100 | 100 |
| VA | 36 | 64 | 100 | - | 100 | 100 | 100 | 100 | - | 100 | - |
| TX | 100 | 99 | 99 | - | - | 100 | - | - | 96 | - | - |

A dash means the board publishes nothing for that field. **Silence is not a negative fact**: OR, VA and TX publish no status column, so `status` is blank there - an empty status does not mean the licence is expired.

### Honest limits

- **One row is one licence held by one person or business.** A holder with several licences appears several times.
- **`licenseType` is the board's own label.** The boards cover more than construction: MN includes electricians and boiler engineers, OH includes HVAC and plumbing, OR includes every CCB endorsement, NJ includes master plumbers and HVACR contractors.
- **NJ covers five contractor boards only** (electrical, home improvement, master plumbers, HVACR, home inspection). The rest of New Jersey's 1.99M professional licences - nursing, cosmetology, medicine and so on - is not in this corpus.
- **VA is filtered to Class A/B/C contractors and tradesmen** (84,610 of DPOR's 257,138 rows). Cosmetologists, real-estate agents, architects and the other DPOR boards are not in this corpus.
- **MN includes "Registered Unlicensed" rows** (about 39% of the state) - Minnesota registers unlicensed contractors as well as licensed ones; `licenseType` says which.
- **FL includes Financial Responsible Officers** - the qualified individual behind a licensed business, not a licence held in that person's own name.
- `status` maps `Expired`, `Inactive`, `Revoked`, `Suspended`/`Susp`, `Terminated`/`Termination`, `Cancelled`, `Out of Business`, `Deceased`, `Closed`, `Retired`, `Passed Away`, `Denied`, `Surrender` and `Superseded` to **inactive**; `Active`, `Current`, `Issued`, `Licensed`, `Valid`, `Clear` (California's word) and `Restored` to **active**; any other word is `unknown`. `status=active` is therefore the normalised active set, not "every row that is not inactive".
- **The corpus is refreshed by the operator**, not live-queried per run; `sourceUpdatedAt` is carried where the board publishes it.
- `status` is normalised from the board's word (`active` / `inactive` / `unknown`); `statusRaw` keeps the original. A board with no status column stays blank.
- Filters a state cannot serve are rejected with a clear error - none are silently dropped.
- The default run (`state=CA`, no filters, `maxResults`  50) returns 50 California rows from a pinned snapshot of the corpus (measured 2026-10-21) and never touches the full store. Add any filter or raise `maxResults` to read the live corpus.
- **Memory**: a non-default query loads one state's shard into memory before filtering (the largest is Minnesota, ~260k rows). Run the actor with at least 1 GB of memory.
- `phone` is the board-published contact number for the licence holder; for a sole proprietor it is also the business number.

### Related

Also available in this account: state-level licence and business-registry actors for Texas, California, Washington, New York and others.

# Actor input Schema

## `state` (type: `string`):

CA = CSLB / WA = L\&I / NJ = DCA contractor boards / FL = DBPR / MN = DLI / OH = OCILB / OR = CCB / VA = DPOR / TX = TDLR contractor types.

## `mode` (type: `string`):

rows = matching licences. aggregate = one count row per group (see groupBy).

## `name` (type: `string`):

Substring match across the holder name and the business name. Not every state fills both - see the coverage table in the README.

## `licenseNumber` (type: `string`):

Exact licence number.

## `licenseType` (type: `string`):

The board's own licence type label, exact (e.g. "Certified Building Contractor", "CONSTRUCTION CONTRACTOR", "Master Plumber"). Availability differs per state.

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

California CSLB class (A, B, C-10, C-20, C-36 ), matched as a whole class token; hyphens are ignored so C10 and C-10 are the same. Other states match this as a substring of their classification/specialty text.

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

active / inactive / unknown (normalised), or the board's own status word (case-insensitive). OR, VA and TX publish no status, so every row there is blank.

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

Substring match.

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

Substring match. MN, VA and WA publish no county.

## `zip` (type: `string`):

Exact ZIP as published (TX has no ZIP in the corpus).

## `groupBy` (type: `string`):

mode=aggregate only (blank = licenseType).

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

Cap on records delivered (0 = default 50; up to 5,000 per run). In aggregate mode it caps the number of GROUP rows returned. Each delivered record is metered individually.

## Actor input object example

```json
{
  "state": "CA",
  "mode": "rows",
  "groupBy": "",
  "maxResults": 50
}
```

# Actor output Schema

## `recordsUrl` (type: `string`):

Contractor licence records - as JSON

## `datasetUrl` (type: `string`):

No description

## `runUrl` (type: `string`):

No description

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("j0401/us-contractor-licenses").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("j0401/us-contractor-licenses").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 '{}' |
apify call j0401/us-contractor-licenses --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,j0401/us-contractor-licenses"
        }
    }
}
```

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/bFlThvORWXw1HJHqx/builds/nxlgHw7Rs5xrOg1U0/openapi.json
