# Contractor License Scraper: New & Suspended WA/OR (`city-signals-data/contractor-license-signals`) Actor

Washington L\&I and Oregon CCB contractor license changes: newly licensed businesses, suspensions, and insurance or bond expiring within N days. One row per change, businesses only, from official open data. For insurance agents, surety agents and suppliers. CSV/JSON.

- **URL**: https://apify.com/city-signals-data/contractor-license-signals.md
- **Developed by:** [City Signals Data](https://apify.com/city-signals-data) (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 Scraper: New & Suspended WA/OR

Contractor License Signals is an Apify Actor by City Signals Data 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` | 500 | Hard cap on rows (you pay per row): $2.50 max by default. The form starts at 100. |

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/city-signals-data/contractor-permit-signals) and [Construction Distress Feed](https://apify.com/city-signals-data/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), with Apify platform usage included. 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.

#### Can I try it for free?

Yes. Apify's free plan includes $5 of usage every month and needs no credit card ([Apify pricing](https://apify.com/pricing)). At $0.005 each, $5 covers about 1,000 records; Apify platform usage is included in that price. The form starts at 100 records ($0.50) for a first look.

#### Can ChatGPT, Claude or another AI agent run this Actor?

Yes. Connect the agent to the [Apify MCP server](https://docs.apify.com/agents) and ask for `city-signals-data/contractor-license-signals` by name. The agent can start a run and read the results as JSON.

### 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.

### More public-record data tools from City Signals Data

- [Trade Show Exhibitor Scraper](https://apify.com/city-signals-data/exhibitor-scraper): first-time and lapsed exhibitors from Map Your Show, a2z and HTML directories.
- [Arizona Mechanics Lien & Lis Pendens Feed](https://apify.com/city-signals-data/construction-distress-feed): weekly Maricopa County liens grouped by company, repeat filers flagged.
- [Building Permits Scraper](https://apify.com/city-signals-data/contractor-permit-signals): new and busy contractors from city open data (Austin, Chicago, Cincinnati, New Orleans, Mesa).
- [SEC Form D Scraper](https://apify.com/city-signals-data/form-d-first-time-raisers): companies filing their first-ever Form D, every business day.

# 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 $0.005 per row). The default 500 caps a run at $2.50; the form starts at 100 ($0.50). 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": 100,
  "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": [],
    "maxRecords": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("city-signals-data/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": [],
    "maxRecords": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("city-signals-data/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": [],
  "maxRecords": 100
}' |
apify call city-signals-data/contractor-license-signals --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,city-signals-data/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/PwUOilddiuGybchEc/openapi.json
