# Contractor License Data: New, Suspended (WA/OR) (`sullivan-ventures/contractor-license-signals`) Actor

Washington and Oregon contractor businesses that are newly licensed, just suspended, or whose insurance or bond expires within N days, from official L\&I and CCB open data. One row per change. Businesses only. JSON, CSV, Excel. More states on request.

- **URL**: https://apify.com/sullivan-ventures/contractor-license-signals.md
- **Developed by:** [Michael Sullivan](https://apify.com/sullivan-ventures) (community)
- **Categories:** Lead generation, Business, Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 contractor signals

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 Data: New, Suspended (WA/OR)

Contractor License Signals is an Apify Actor by Sullivan Ventures that reads official Washington L\&I and Oregon CCB license data and lists contractor businesses that were just licensed, just suspended, or have insurance or a bond expiring within a set number of days. It's built for insurance agents, surety and bond agents, and suppliers who need to know what changed, not just a license's status.

Know which contractor businesses just got licensed, just got suspended, or have insurance or a bond running out. This Actor reads the official Washington L\&I and Oregon CCB contractor-license open data every time you run it and gives you one row per signal:

- **new_license**: newly licensed contractor business (WA license effective date, OR first registration date) in the last N days. In Washington you also get `prior_licenses_same_ubi` (0 = the business's first contractor license).
- **suspended**: Washington licenses suspended in the last N days.
- **insurance_expiring**: the latest liability policy on file expires within N days (default 30). Washington and Oregon.
- **bond_expiring**: the contractor bond on file expires within N days (Oregon).
- **left_active_list**: an Oregon license that was on the CCB active list at your last run and is gone now (lapsed, suspended, revoked or inactive). Starts working from your second run.

Plain license lookups tell you a status. This tells you **what changed** and **what is about to change**.

### Who uses it

- **Commercial insurance and surety bond agents**: contractors whose policy or bond is about to lapse, with the current insurer and agency.
- **Building-material suppliers, equipment rental, contractor software**: brand-new contractor businesses in your area, before anyone else calls.
- **Credit departments, general contractors, sureties**: subcontractors that were just suspended or dropped off the active list.

### Sizes from a test run (Oct 23, 2026, all signals, both states)

| Signal | Washington | Oregon |
|---|---|---|
| New licenses, last 7 days | 185 | 89 |
| Suspended, last 7 days | 274 | (see left_active_list) |
| Insurance expiring in 30 days | 3,433 | 1,638 |
| Bond expiring in 30 days | not used | 736 |

About 6,350 rows in total. 986 individuals / sole proprietors were dropped.

### Sources (official, no login, no captcha)

| State | Dataset | License |
|---|---|---|
| Washington | data.wa.gov `m8qx-ubtq` L\&I Contractor License Data - General | Open Data Commons PDDL 1.0 (public domain) |
| Washington | data.wa.gov `ciwg-agsx` L\&I Contractor License Data - Insurance | Public Domain |
| Oregon | data.oregon.gov `g77e-6bhs` CCB Active Licenses | Public Domain U.S. Government works |

Washington's separate bond dataset is **not** used because no license is stated on it. Source: Washington State Department of Labor & Industries; Oregon Construction Contractors Board. This Actor is not affiliated with either agency. Data is as published by the state; check the official record before acting on it.

### Businesses only (privacy by design)

- Washington: only business types LLC, Corporation, Partnership, LLP, Association, Joint Venture or Trust. Records with business type **Individual** are dropped.
- Oregon has no business-type column, so only names with a legal-entity suffix (LLC, INC, CORP, CO, LTD, LP, LLP, PLLC, PC) are kept. Sole proprietors and individual certifications (home inspectors, locksmiths) are dropped.
- Principal / responsible-managing-individual person names are never output. Address and phone are the business's address and phone of record.

### Input

| Field | Default | What it does |
|---|---|---|
| `states` | `["wa","or"]` | wa, or |
| `signals` | all five | new_license, suspended, insurance_expiring, bond_expiring, left_active_list. Order = priority when the cap is hit |
| `newWithinDays` | 7 | New licenses in the last N days |
| `suspendedWithinDays` | 7 | WA suspensions in the last N days |
| `expiringWithinDays` | 30 | Insurance / bond expiring between today and today + N |
| `cities` | all | Business-address cities, e.g. `["SEATTLE","TACOMA"]` |
| `counties` | all | Oregon counties, e.g. `["Multnomah","Lane"]` |
| `onlyNewSinceLastRun` | false | Only signals your earlier runs did not output |
| `maxRecords` | 1000 | Hard cap on rows (you pay per row) |

Example: weekly lapsing-insurance list for Portland-area contractors:

```json
{ "states": ["or"], "signals": ["insurance_expiring", "bond_expiring"], "counties": ["Multnomah", "Washington", "Clackamas"], "expiringWithinDays": 30, "onlyNewSinceLastRun": true }
```

Schedule it weekly with `onlyNewSinceLastRun: true` and you only pay for new signals. State lives in a named key-value store in your account (`contractor-license-state`).

### Output

**Dataset**: one row per contractor signal. Sample:

```json
{
  "signal": "insurance_expiring",
  "signal_date": "2026-10-24",
  "days_until_expiry": 1,
  "state": "OR",
  "company_name": "AMERICAN CINEMA EQUIPMENT INC",
  "company_key": "AMERICAN CINEMA EQUIPMENT",
  "license_number": "140782",
  "license_type": "Commercial Specialty Contractor Level 1",
  "license_status": "ACTIVE",
  "license_expiration_date": "2028-02-08",
  "first_registered": "2000-02-08",
  "address": "11802 SE STARK ST",
  "city": "PORTLAND",
  "zip": "97216",
  "county": "Multnomah",
  "phone": "5032857015",
  "insurance_company": "MID CENTURY INSURANCE CO",
  "insurance_amount_usd": 2000000,
  "insurance_expiration_date": "2026-10-24",
  "bond_company": "OHIO CASUALTY INSURANCE CO",
  "bond_amount_usd": 55000,
  "bond_expiration_date": "2028-02-08",
  "is_new_since_last_run": true,
  "source_url": "https://data.oregon.gov/d/g77e-6bhs",
  "data_license": "Public Domain U.S. Government works (data.oregon.gov), Oregon Construction Contractors Board"
}
```

```json
{
  "signal": "new_license",
  "signal_date": "2026-10-16",
  "state": "WA",
  "company_name": "1887575 ALBERTA INC",
  "business_type": "Corporation",
  "license_number": "18875AI741O3",
  "license_type": "CONSTRUCTION CONTRACTOR",
  "license_effective_date": "2026-10-16",
  "specialties": ["GENERAL"],
  "ubi": "606204144",
  "prior_licenses_same_ubi": 0,
  "city": "EDMONTON",
  "phone": "7804380605",
  "source_url": "https://data.wa.gov/d/m8qx-ubtq"
}
```

**Key-value store**: `SIGNALS_CSV` (same rows as CSV) and `SUMMARY` (counts per state and signal, drops, rate-limit log).

`company_key` uses the same normalization as [Contractor Permit Signals](https://apify.com/sullivan-ventures/contractor-permit-signals) and [Construction Distress Feed](https://apify.com/sullivan-ventures/construction-distress-feed), so you can join new or lapsing licenses with permit activity and liens.

### Pricing

Pay per event: **$5 per 1,000 records** ($0.005 per dataset row). No start fee. Set "Max cost per run" and the Actor stops at your limit, keeping your top-priority signals first. Use `maxRecords`, filters and `onlyNewSinceLastRun` to keep runs small.

### Works in Clay / Zapier / Make

Run it from Clay (Apify integration), Zapier or Make with the official Apify apps, or call it through the Apify API and schedule it weekly. Each run's dataset is available as JSON, CSV or Excel.

### FAQ

#### How do I find newly licensed contractors in Washington or Oregon?

Run this Actor with the new_license signal. It reads the official Washington L\&I and Oregon CCB data and returns businesses licensed in the last N days. In the Oct 23, 2026 test, that was 185 in Washington and 89 in Oregon for the past 7 days.

#### Which contractors have insurance expiring soon?

Choose the insurance_expiring signal and a window (default 30 days). The Actor returns each business whose latest liability policy on file expires in that window: 3,433 in Washington and 1,638 in Oregon in the Oct 23, 2026 test. That's a renewal lead list for insurance agents.

#### Can I get alerts when a contractor license is suspended?

Yes. Schedule the Actor (daily or weekly) with the suspended signal for Washington. In Oregon, left_active_list flags licenses that dropped off the active list since your last run. Turn on onlyNewSinceLastRun to get only new changes.

#### How much does it cost?

$5 per 1,000 records ($0.005 per row) plus Apify platform usage. Set a max cost per run; the Actor stops at your limit and keeps your top-priority signals first.

#### Does it cover other states?

Today it covers Washington and Oregon. Other US states, and some countries, publish contractor license data in different forms, and we add them on request when the data is public and allowed for reuse. Coverage: we confirm data availability for your state before you run it; ask in the Issues tab.

### Limits

- `insurance_expiring` means the latest policy **on file** expires in the window. A renewal may already be in progress and not yet filed.
- Oregon publishes active licenses only, so Oregon suspensions appear as `left_active_list` (from your second run on).
- New license dates are when the state license took effect; a "new" license can be an existing company newly licensed in that state.
- A full both-states run takes under a minute and about 90 API calls.

# Actor input Schema

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

Official state contractor-license datasets to read.

## `signals` (type: `array`):

Which signals to output. Order sets output priority when the record cap is hit.

## `newWithinDays` (type: `integer`):

License effective (WA) or first registered (OR) within the last N days.

## `suspendedWithinDays` (type: `integer`):

WA suspensions dated within the last N days.

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

Latest insurance policy (WA, OR) or bond (OR) on file expires between today and today + N days.

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

Only contractors whose business address is in these cities, e.g. SEATTLE, PORTLAND. Empty = all.

## `counties` (type: `array`):

Oregon only, e.g. Multnomah, Washington, Lane. Empty = all.

## `onlyNewSinceLastRun` (type: `boolean`):

Skip signals already output by your previous runs (state kept in a named key-value store in your account).

## `maxRecords` (type: `integer`):

Hard cap on dataset rows (you pay per row). A full WA+OR run with all signals is about 6,000 rows.

## `updateState` (type: `boolean`):

Save seen signals and the Oregon active list for 'new since last run' and 'left active list'.

## `stateStoreName` (type: `string`):

Named key-value store used for run-to-run state.

## `socrataAppToken` (type: `string`):

Free token from data.wa.gov / data.oregon.gov to raise rate limits. Not needed for normal use.

## `requestDelayMs` (type: `integer`):

Politeness delay between requests.

## Actor input object example

```json
{
  "states": [
    "wa",
    "or"
  ],
  "signals": [
    "new_license",
    "suspended",
    "insurance_expiring",
    "bond_expiring",
    "left_active_list"
  ],
  "newWithinDays": 7,
  "suspendedWithinDays": 7,
  "expiringWithinDays": 30,
  "cities": [],
  "counties": [],
  "onlyNewSinceLastRun": false,
  "maxRecords": 1000,
  "updateState": true,
  "stateStoreName": "contractor-license-state",
  "requestDelayMs": 250
}
```

# Actor output Schema

## `signals` (type: `string`):

One row per contractor signal.

## `signalsCsv` (type: `string`):

Same rows as CSV.

## `summary` (type: `string`):

Counts per state and signal, drops, rate-limit log, data limits.

# 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": [
        "wa",
        "or"
    ],
    "signals": [
        "new_license",
        "suspended",
        "insurance_expiring",
        "bond_expiring",
        "left_active_list"
    ],
    "cities": [],
    "counties": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("sullivan-ventures/contractor-license-signals").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": [
        "wa",
        "or",
    ],
    "signals": [
        "new_license",
        "suspended",
        "insurance_expiring",
        "bond_expiring",
        "left_active_list",
    ],
    "cities": [],
    "counties": [],
}

# Run the Actor and wait for it to finish
run = client.actor("sullivan-ventures/contractor-license-signals").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 '{
  "states": [
    "wa",
    "or"
  ],
  "signals": [
    "new_license",
    "suspended",
    "insurance_expiring",
    "bond_expiring",
    "left_active_list"
  ],
  "cities": [],
  "counties": []
}' |
apify call sullivan-ventures/contractor-license-signals --silent --output-dataset

```

## MCP server setup

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

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/mwGYGWr0kxeNAf1iW/builds/D2J6Zd0dlBPGe4f3o/openapi.json
