# California Contractor License Lookup & Export (`wellbuilt_zythem/california-contractor-license-lookup`) Actor

Every CSLB-licensed contractor in California with phone on 99.8 % of rows, measured. Export a whole classification (roofing, electrical, plumbing, HVAC, solar) or verify a list of names or licence numbers. Status, expiry date, bond, address. Official register.

- **URL**: https://apify.com/wellbuilt\_zythem/california-contractor-license-lookup.md
- **Developed by:** [Carlos Escanez](https://apify.com/wellbuilt_zythem) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $9.00 / 1,000 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.

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

## California Contractor License Lookup & Export — Contractors State License Board (CSLB)

**Paste a list of names or licence numbers and get one row per matching California licence. Or export every licensed roofing contractor in California in one run.** Read straight from the official register, normalised into one clean row.

### See it run

https://youtu.be/07g\_67B52DM

### Questions this answers

- Is this roofing contractor's license active in California right now, and when does it expire?
- Check these 500 subcontractor names against the California board before we put them on site.
- Export every licensed roofing, electrical, plumbing contractor in California with a phone number.
- Which California licences expire in the next 60 days?

### What California actually publishes

Measured on real rows pulled from the board, not on what its documentation says. **A field the board does not publish stays empty; the Actor never guesses one.**

| Field | Published |
|---|---|
| Licence number, status, trade | yes |
| Expiry date | yes |
| Street address | yes |
| City and ZIP | yes |
| County | yes |
| Phone | yes |
| Email | no |
| Insurance or bond expiry | no |

**California publishes a phone number on nearly every row.** That makes this a calling list as well as a verification tool.

### Trades covered

- General Building / Residential Remodeling
- Electrical
- Plumbing
- Warm-Air Heating, Ventilating, Air-Conditioning / Refrigeration
- Roofing
- Swimming Pool
- Asbestos Abatement
- Elevator
- Limited Specialty

A trade the board does not license is not in the list, and the Actor will not invent it.

### Two modes

**Lookup.** One name or licence number per line. A licence number matches exactly; a name matches any licensee or business containing it. Every match comes back as its own row with the query it matched, so a list of 800 subcontractors comes back as a checked list.

**Export.** Pick a trade (or all of them) and get every licence in California, with `onlyActive`, `onlyWithPhone` and `onlyExpiringSoon` (next 60 days) filters and a `maxResults` cap.

### What every row carries

`state`, `licenseNumber`, `name`, `businessName`, `tradeLabel`, `status`, `isActive`, `expirationDate`, `daysToExpiry`, `isExpiringSoon`, `address`, `city`, `zip`, `county`, `phone`, `email`, `sourceUrl`, `scrapedAt`, plus `matchedQuery` in lookup mode. Dates are ISO, phones are E.164, status is normalised so `ACTIVE` means active on every board.

### Full detail: the record the bulk file cannot give

Pass licence numbers in **full detail** mode and each one comes back with the complete CSLB record: **bond** company, number, amount and effective date; **workers' compensation** carrier, policy number, expiry and classification codes; every classification; entity type (Corporation, Sole Ownership); and today's status in the board's own words.

Why it matters, measured 19 September 2026: the CSLB bulk file publishes **active licences only**. The licence page answers for the rest. Licence 400000 is \`EXPIRED\`, 500123 \`INACTIVE\`, 800000 \`CANCELLED\`, and 99999999 is \`found: false\` because it is not in the register. If you check a subcontractor before they start work, those answers are the job, and no bulk file gives them.

Every number comes back as a row: \`checked: true, found: true\` (charged), \`checked: true, found: false\` (not in the register, **not charged**), or \`checked: false\` (the board did not answer, **not charged**, and the run reports itself incomplete). Five licences take about five seconds. Add \`withDetail\` to a lookup or export to get the same record on every row found, up to 500 pages per run.

### Built for verification

- **Every query comes back as a row**, matched or not, with `matchedQuery` to join it to your list.
- **No match, no charge.**
- **Status normalised** so `isActive` means the same thing as on every other board; the board's own status text is kept.
- **Source URL on every row**, pointing at the board's page for that licence: the primary source for a compliance file.
- **Coverage printed by the run**, per field, in `RUN_SUMMARY`: what you actually received, not what a README promises.
- **Incomplete runs say so**: a board that did not answer is named in the summary and the run is flagged, never passed off as clean.
- **Stable ids** (`CA-<licence>`) for diffing between scheduled runs: schedule the same list daily and you have licence-lapse alerts on your subcontractors.

### Pricing

Pay per delivered row. Filtered-out rows cost nothing. A lookup that finds nothing charges nothing beyond the start fee.

### Need more states?

The same tool covers 20 states in one query, with a per-state coverage table so you know before you buy which boards publish a phone or an email: https://apify.com/wellbuilt\_zythem/us-contractor-license-lookup

### Data source

Contractors State License Board (CSLB). A public regulatory register published so that anyone can verify a licence; contact details are the business contacts the board publishes for that purpose.

# Actor input Schema

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

Lookup verifies a list of names or licence numbers. Export downloads a whole state and trade. Full detail reads the CSLB licence page for each California licence number you pass: bond, workers' compensation carrier and policy, classifications, entity type and today's status. It is the only mode that answers for a licence that is expired, inactive, cancelled or not in the register at all, because the bulk California file publishes active licences only.

## `queries` (type: `array`):

One per line. A licence number matches exactly; a name matches any California licensee or business containing it.

## `withDetail` (type: `boolean`):

For California rows, also read the licence page of each result: bond company, number, amount and effective date, workers' compensation carrier, policy number and expiry, all classifications, entity type and today's status. One request to the CSLB per licence, so it is capped by "Max full details per run". Enriched rows are charged as a full detail; the rest as a normal licence record. Never both for the same row.

## `maxDetails` (type: `integer`):

How many licence pages to read in one run. Each one is a request to the CSLB, so the hard limit is 500 per run whatever you put here. For volume without the detail, use export mode.

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

Leave empty for every trade the California board licenses.

## `onlyActive` (type: `boolean`):

Drop expired, suspended and out-of-business licences.

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

For calling lists.

## `onlyExpiringSoon` (type: `boolean`):

The renewal window. Who sells bonds, insurance or continuing education wants exactly these.

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

Keep only licences that expire in the next N days (1 to 365). The renewal list: whoever sells bonds, insurance or continuing education calls these first. Leave empty for no window. Overrides the fixed 60-day "expiring soon" switch when set.

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

Keep only licences first issued in the last N days (1 to 365): newly licensed contractors, which is a new business needing suppliers, software, a truck and insurance. Uses the board's issue date; boards that do not publish one return nothing here rather than a guess.

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

0 for no limit.

## Actor input object example

```json
{
  "mode": "lookup",
  "queries": [
    "Johnson Electric",
    "17001"
  ],
  "withDetail": false,
  "maxDetails": 100,
  "trades": [],
  "onlyActive": false,
  "onlyWithPhone": false,
  "onlyExpiringSoon": false,
  "maxResults": 0
}
```

# Actor output Schema

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

One row per licence matched (lookup), per licence in the state and trade (export), or per licence number checked (California full detail, which adds bond, workers' compensation and classifications). Normalised across states: status, expiry, days to expiry, phone, address. A California number that is not in the register comes back with found: false and is not charged.

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

Rows read and delivered per state, which registries failed to respond, and how many rows were charged.

# 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 = {
    "queries": [
        "Johnson Electric",
        "17001"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("wellbuilt_zythem/california-contractor-license-lookup").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 = { "queries": [
        "Johnson Electric",
        "17001",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("wellbuilt_zythem/california-contractor-license-lookup").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 '{
  "queries": [
    "Johnson Electric",
    "17001"
  ]
}' |
apify call wellbuilt_zythem/california-contractor-license-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,wellbuilt_zythem/california-contractor-license-lookup"
        }
    }
}
```

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/UJpA9dAwfrVDbTEju/builds/KxGSHoBGtL7Faq4rx/openapi.json
