# Contractor License Check: CA/WA/TX (official files) (`pelicanlabs/contractor-license-check`) Actor

Is this contractor licensed, active, bonded and insured right now? Checks California CSLB, Washington L\&I and Texas TDLR from the states' own published files. Feed it licence numbers, business names, or a Google Maps scraper dataset. $0.02 per matched licence, not-found rows are free.

- **URL**: https://apify.com/pelicanlabs/contractor-license-check.md
- **Developed by:** [Jacob](https://apify.com/pelicanlabs) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 88.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 license matches

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

## Contractor License Check: CA/WA/TX (official files)

Is this contractor licensed, active, bonded and insured right now? This actor checks contractors against the state boards' own published licence files:

| State | Source | What you get |
|---|---|---|
| California | CSLB License Master list (free official download) | status (incl. suspensions), classifications, issue/expiry, contractor bond, workers' comp |
| Washington | L\&I contractor, bond and insurance datasets on data.wa.gov | status (active, expired, suspended...), specialty, expiry, bond, general liability insurance |
| Texas | TDLR All Licenses on data.texas.gov (A/C, electrical, sign, appliance, elevator, water well contractor types) | licence type and expiry; TDLR publishes no status, bond or insurance column |

No live board pages are scraped and no CAPTCHAs are touched. Every row carries `source_as_of` (the date of the state's file) and `source_url`.

### Input

- `queries`: a list of `{"state": "CA", "license_number": "1000004"}` or `{"state": "WA", "business_name": "04 Construction LLC"}`.
- `datasetId`: the ID of a Google Maps scraper dataset. Each place's `title` is checked as a business name in its `state`; the place's address, phone and URL are copied to the output row.

### Output (one row per query)

`verdict` (`active`, `expired`, `suspended`, `not_found`), `match_confidence`, `license_number`, `business_name`, `dba`, `license_type`, `classifications`, `status`, `expiration_date`, `bonded`, `bond_amount`, `workers_comp`, `liability_insured`, `source_url`, `source_as_of`, `possible_matches`.

`null` means the state does not publish that field. Name matches scoring under 0.8 are listed in `possible_matches` and the verdict stays `not_found`.

### Pricing

$0.02 per matched licence. Rows that come back `not_found` are free.

### Integrations

Use this actor from n8n, Make, Google Sheets, Clay or Zapier with your own Apify token (Apify Console > API & Integrations). Every integration makes the same call:

```
POST https://api.apify.com/v2/acts/pelicanlabs~contractor-license-check/run-sync-get-dataset-items?memory=512
Authorization: Bearer <your Apify token>
Content-Type: application/json

{"queries":[{"state":"CA","license_number":"1000004"},{"state":"WA","business_name":"04 Construction LLC"}],"maxItems":2}
```

The response is a JSON array with one item per input row. The main fields are `verdict`, `license_number`, `business_name`, `dba`, `status`, `classifications`, `expiration_date`, `bonded`, `workers_comp`, `liability_insured`, `match_confidence`, `source_url`, `source_as_of`.

**Send a list in one request.** Each request is one actor run, and each run has a start-up time of several seconds and a small Apify start fee. A batch of 500 rows costs the same start-up as a batch of 1. The n8n workflow and the Google Sheets menu send batches. Clay, Zapier and the Sheets formula send one row per run, which suits small volumes.

| Tool | What you get | Download |
|---|---|---|
| n8n | Workflow to import: reads a Google Sheet, sends up to 500 unchecked rows in one run, writes the results back | [contractor-license-check.json](https://contractor-license-check.pages.dev/integrations/n8n/contractor-license-check.json) |
| Make | Blueprint (HTTP module, one run per batch); setup steps for the new HTTP app are below | [contractor-license-check.blueprint.json](https://contractor-license-check.pages.dev/integrations/make/contractor-license-check.blueprint.json) |
| Google Sheets | Apps Script: `=CONTRACTOR_CHECK("1000004", "CA")` (licence number or business name) for single cells, and a menu item that checks a selected range in batches of 200 | [pelican-checks.gs](https://contractor-license-check.pages.dev/integrations/google-sheets/pelican-checks.gs) |
| Clay | HTTP API enrichment column (steps below) | - |
| Zapier | Webhooks by Zapier, Custom Request (steps below) | - |

**n8n:** in the workflow menu (three dots), choose Import from File, or Import from URL with the link above. Open *Run actor (run-sync)* and create a **Header Auth** credential with Name `Authorization` and Value `Bearer <your token>`. In *Read rows* and *Write results*, choose your sheet. Row 1 needs the columns `License or Name`, `State` plus the result columns listed in the workflow's note.

**Make:** create a scenario, open the three-dots menu, choose Import Blueprint and select the file. In the HTTP module, add an API key keychain that sends `Authorization: Bearer <your token>` in the header. Edit `input_json` in the first module, or map it from your sheet. Then add an Iterator on the HTTP module's `Data` to get one bundle per row. To build the same flow on the new HTTP app (version 4) instead, use the *Make a request* module with the method, URL, header and body shown above.

**Google Sheets:** in your sheet, open Extensions > Apps Script, paste the file and save, then reload the sheet. Choose *Pelican checks > Set Apify token*. Then either type the formula, or select your rows and choose *Check selected rows: contractor licence* (select two columns: licence or name, state). Results are written to the columns to the right of your selection. Rows that already have a result are skipped, so you can run it again to continue. A formula must finish within Google's 30-second limit for custom functions, so use the menu for anything more than a few rows.

**Clay:** Add enrichment > HTTP API > Configure.

- Method: `POST`
- Endpoint: `https://api.apify.com/v2/acts/pelicanlabs~contractor-license-check/run-sync-get-dataset-items?memory=512&timeout=60`
- Headers: `Authorization` = `Bearer <your token>`. You can save this as a header account so the token is not shown in the table.
- Body: `{"queries": [{"state": "/State", "license_number": "/License Number"}]}`. Replace the `/Column Name` parts with your own columns: type `/` to insert one. To look up by business name instead of licence number, use `business_name` in place of `license_number`.
- Clay sends one request, and so one actor run, per row. The response is an array with one item. Run a single row first, then add the fields you want (`verdict` first) as columns.

**Zapier:** use the *Webhooks by Zapier* action *Custom Request*.

- Method: `POST`
- URL: `https://api.apify.com/v2/acts/pelicanlabs~contractor-license-check/run-sync-get-dataset-items?memory=512&timeout=25`
- Data: the JSON body above, with your Zap's fields mapped in.
- Headers: `Authorization` = `Bearer <your token>` and `Content-Type` = `application/json`.
- Zapier stops a step after about 30 seconds, so send one row per Zap run. Apify's own Zapier app also has *Run Actor and Fetch Results*, which works if you prefer it.

Pricing: $0.02 per matched licence (not-found rows are free), billed by Apify to the account that owns the token.

### Limits

- California's master list excludes cancelled, revoked and expired non-renewable licences; those return `not_found`.
- Texas status is derived from the expiry date only.
- Not a certificate of licensure. Status can change at any time; confirm on the board's site before relying on a result.

# Actor input Schema

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

Each item: {"state": "CA" | "WA" | "TX", "license_number": "..."} or {"state": ..., "business_name": "..."}.

## `datasetId` (type: `string`):

ID of a dataset from a Google Maps scraper run. Each place's `title` is checked as the business name in its `state`.

## `defaultState` (type: `string`):

Used when a query or place has no state.

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

Stop after this many queries (cap 10,000).

## Actor input object example

```json
{
  "queries": [
    {
      "state": "CA",
      "license_number": "1000004"
    },
    {
      "state": "WA",
      "business_name": "04 Construction LLC"
    },
    {
      "state": "TX",
      "license_number": "10001"
    }
  ],
  "maxItems": 1000
}
```

# Actor output Schema

## `verdicts` (type: `string`):

One row per query: verdict, matched licence record, source link.

## `verdictsTable` (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 = {
    "queries": [
        {
            "state": "CA",
            "license_number": "1000004"
        },
        {
            "state": "WA",
            "business_name": "04 Construction LLC"
        },
        {
            "state": "TX",
            "license_number": "10001"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("pelicanlabs/contractor-license-check").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": [
        {
            "state": "CA",
            "license_number": "1000004",
        },
        {
            "state": "WA",
            "business_name": "04 Construction LLC",
        },
        {
            "state": "TX",
            "license_number": "10001",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("pelicanlabs/contractor-license-check").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": [
    {
      "state": "CA",
      "license_number": "1000004"
    },
    {
      "state": "WA",
      "business_name": "04 Construction LLC"
    },
    {
      "state": "TX",
      "license_number": "10001"
    }
  ]
}' |
apify call pelicanlabs/contractor-license-check --silent --output-dataset

```

## MCP server setup

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

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/PEWbJhVdVtc4qlYEz/builds/iIIiTVzZIkJYyQPdL/openapi.json
