# Contractor License Feed: New & Suspended, WA and OR (`quaky_ozone/contractor-license-feed`) Actor

New and suspended contractor licenses from Washington L\&I and Oregon CCB official data: company, specialty, phone, bond and insurance dates. Daily.

- **URL**: https://apify.com/quaky_ozone/contractor-license-feed.md
- **Developed by:** [Z J](https://apify.com/quaky_ozone) (community)
- **Categories:** Lead generation, Business
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 results

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 Feed: New & Suspended Contractors, WA and OR (Companies Only)

Get newly licensed and newly suspended **construction contractors in Washington and Oregon**, pulled from the official open data of Washington Labor & Industries (L\&I) and the Oregon Construction Contractors Board (CCB). Each row is one contractor **company**: business name, license type and specialty, status, expiration, business phone and filed address, plus Oregon bond and insurance companies and expiration dates. Washington rows typed Individual (sole proprietors), any row whose business name fails the strict company-name test, and principal or responsible-managing-individual names are never returned. Oregon has no business-type column, so there only the name test applies.

### What you get

| Field | Meaning |
|---|---|
| `eventDate`, `event` | `licensed`: the license effective date (WA) or the license's first registration date (OR, the earliest of its license classes) falls in your window. `suspended`: the WA suspension date falls in your window |
| `state`, `licenseNumber` | Licensing state (`WA` or `OR`) and the board's license number |
| `businessName`, `businessType` | The licensed company; `businessType` as WA publishes it (Limited Liability Company, Corporation, ...). Oregon has no type column |
| `licenseType`, `specialties` | WA license type and specialty 1/2; OR license class codes and endorsement texts, joined with `; `. In both states, a text containing a non-ASCII character, an '@', a phone-like run of 7 or more digits or a common first name is left out |
| `licenseStatus`, `expirationDate` | Current WA status (`ACTIVE`, `SUSPENDED`, ...). Oregon's dataset lists active licenses only, so OR rows say `ACTIVE` |
| `bondCompany`, `bondAmount`, `bondExpirationDate` | Oregon only: surety bond company (blank unless it is one proven organisation), amount and expiration |
| `insuranceCompany`, `insuranceAmount`, `insuranceExpirationDate` | Oregon only: liability insurer (blank unless it is one proven organisation), amount and expiration |
| `street`, `city`, `region`, `postalCode`, `county` | The address the company filed with the board (`county` Oregon only). When the street is withheld (see Privacy), city, region and ZIP are blank too, so `cities` and `zipPrefix` filters skip that row |
| `phone`, `ubi` | Business phone as filed; `ubi` is Washington's unified business id |
| `changeType` | `new`, or `changed` when a license you already received changed event, status, expiration, name, specialty, phone, street, insurer, or bond or insurance expiration |
| `sourceDataset`, `sourceUrl`, `sourceFreshness`, `retrievedAt` | Where the row came from (a link to the official record), the newest event date that source had, and when you pulled it |

### Who it's for

- **Contractor insurance and surety bond agents**: new licensees need coverage; suspensions and coming insurance or bond expirations are renewal and replacement signals.
- **Building-supply, equipment-rental and trade distributors** opening accounts with new contractors.
- **Payroll, estimating, field-service and accounting software vendors** selling to new construction businesses.
- **General contractors and property managers** checking that a subcontractor's license is still active.

### Input

| Field | Default | Notes |
|---|---|---|
| `states` | both | `WA`, `OR` |
| `events` | `licensed` | `licensed`, `suspended` (Washington only) |
| `daysBack` | 7 | 1-90, counted back from today |
| `sinceDate` | none | `YYYY-MM-DD`; overrides `daysBack` (never more than 90 days back) |
| `specialtyContains` | none | e.g. `ELECTRICAL`, `ROOFING`, `Residential`; case-insensitive |
| `cities` | all | Exact city names, case-insensitive |
| `zipPrefix` | none | e.g. `981` for Seattle-area ZIP codes |
| `onlyNewSinceLastRun` | `true` | See "Repeat runs" below |
| `maxItems` | 1000 | 1-20,000 rows per run, newest first |

Daily feed of new contractors in both states:

```json
{ "states": ["WA", "OR"], "events": ["licensed"], "daysBack": 7, "onlyNewSinceLastRun": true }
```

Washington suspensions this week (insurance and bond agents):

```json
{ "states": ["WA"], "events": ["suspended"], "daysBack": 7 }
```

New residential contractors around Portland:

```json
{ "states": ["OR"], "specialtyContains": "Residential", "zipPrefix": "972", "daysBack": 30 }
```

### Output example

Values below are illustrative; real rows have the same shape.

```json
[
  { "eventDate": "2026-10-19", "event": "licensed", "state": "WA", "licenseNumber": "EXAMPLL700AA", "businessName": "EXAMPLE ROOFING LLC",
    "businessType": "Limited Liability Company", "licenseType": "CONSTRUCTION CONTRACTOR", "specialties": "ROOFING",
    "licenseStatus": "ACTIVE", "expirationDate": "2028-10-19", "street": "100 EXAMPLE AVE STE 1", "city": "SPOKANE", "region": "WA",
    "postalCode": "99201", "phone": "5095550100", "ubi": "600000001", "changeType": "new", "sourceDataset": "m8qx-ubtq",
    "sourceUrl": "https://data.wa.gov/resource/m8qx-ubtq.json?contractorlicensenumber=EXAMPLL700AA",
    "sourceFreshness": "2026-10-19", "retrievedAt": "2026-10-19T12:00:00+00:00" },
  { "eventDate": "2026-10-12", "event": "licensed", "state": "OR", "licenseNumber": "240000", "businessName": "EXAMPLE CONVEYOR SYSTEMS INC",
    "licenseType": "CGC1; RGC", "specialties": "Commercial General Contractor Level 1; Residential General Contractor",
    "licenseStatus": "ACTIVE", "expirationDate": "2028-10-12", "bondCompany": "EXAMPLE SURETY COMPANY", "bondAmount": "20000",
    "bondExpirationDate": "2028-10-12", "insuranceCompany": "EXAMPLE INSURANCE COMPANY", "insuranceAmount": "1000000",
    "insuranceExpirationDate": "2027-07-14", "city": "SALEM", "region": "OR", "county": "Marion", "changeType": "new",
    "sourceDataset": "g77e-6bhs", "sourceUrl": "https://data.oregon.gov/resource/g77e-6bhs.json?license_number=240000" }
]
```

### Pricing

Pay per result: **$0.0024 per license row** ($2.40 per 1,000 rows), charged once per row returned. Apify also charges its standard actor-start event (default $0.00005, i.e. $0.05 per 1,000 runs) once per run. You are never charged for the run summary or for errors; those go to the run's key-value store (`OUTPUT`), not the dataset. A run stops cleanly when it reaches the spending limit you set.

**Repeat runs.** With `onlyNewSinceLastRun` on (the default), the actor remembers which licenses this exact input already returned, in a storage that stays in your account. A daily schedule only returns licenses that are new or that changed (for example a Washington license that is later suspended, or an Oregon contractor that switches insurer). The memory forgets a license it has not seen for 45 days (or 3 × `daysBack`, if longer), so `sinceDate` runs spaced further apart than that return and charge those licenses again as new. Rows are only deduplicated between runs that follow one another: two runs of the same input that overlap in time, or a run interrupted after it delivered rows but before it saved its memory, can return and charge some rows again.

Free-plan users can try it within Apify's monthly free usage credit.

### Freshness and limits, stated honestly

| State | Source cadence | What that means |
|---|---|---|
| Washington | Updated three times a day | `licensed` uses L\&I's license effective date. In samples seen on 2026-10-19 a renewed license kept its original effective date, so these look like new licenses, but L\&I does not document it; treat an older business with a fresh date as possibly re-licensed. `suspended` uses the suspension date; `licenseStatus` shows the status now, so a license reinstated since shows `ACTIVE` |
| Oregon | Daily | The CCB dataset lists active licenses only, so Oregon supports `licensed` (first registration date) and never `suspended`. A license is returned only when its earliest active class is in your window, so an older contractor that just added a class (for example a lead-based paint renovation class) is not reported as new. Asking for suspended Oregon rows returns none and says so in the run's `OUTPUT` notes |

**Companies only means some real companies are withheld.** Rows and names that cannot be proven to be one organisation are withheld, not guessed. In 7-day samples on 2026-10-19, roughly 6 in 10 new Washington licenses (115 of 193 in the latest run) and 6 to 8 in 10 Oregon licenses passed the company test below. Left out: sole proprietors; businesses whose name carries no legal form (for example 'EXAMPLE CONSTRUCTION'); names that join two parts with '&' where the first reads like a person's name (for example 'ALL FENCE & REPAIR LLC'); and names that fail the allowlist in Privacy below, such as any symbol other than `& , . ' -`, '&' glued between longer words, a listed word like 'by', 'for', 'ta', 'family', 'estate', 'member' or 'md', 'CO' before the last word, or two single letters in a row (for example 'BELLEVUE MASONRY/DRAINAGE LLC', 'EVERGREEN FLOORING #1 LLC', 'KARPSTEIN BUILD\&REPAIR LLC', 'BB REAL ESTATE SERVICES LLC', 'GARAGE DOOR MD LLC', 'THE DRYWALL REPAIR CO LLC' or 'TA CONSTRUCTION LLC'), even when the business is real. A surety or insurer name without that proof (for example an abbreviation like 'CBIC', or 'SL - KINSALE INSURANCE COMPANY') is left blank. A business named after its owner with a legal form (for example 'EXAMPLE SMITH PAINTING LLC') is an organisation's legal name and is returned.

Each run's `OUTPUT` record lists every source's status, row count and newest date; one state failing never stops the other.

### Data sources, license and attribution

- **Washington**: "L\&I Contractor License Data - General", dataset `m8qx-ubtq` on data.wa.gov, published by the Washington State Department of Labor & Industries. License: Open Data Commons Public Domain Dedication and License (PDDL). Attribution: Labor & Industries.
- **Oregon**: "CCB Active Licenses", dataset `g77e-6bhs` on data.oregon.gov, published by the Oregon Construction Contractors Board. License: labelled by the publisher on data.oregon.gov as "Public Domain U.S. Government" (that label's linked page notes the U.S. government-work designation does not itself cover state works; no terms restricting reuse were found).

**Source and version.** Every row carries `sourceUrl`, a link to that license's record in the official dataset, and `retrievedAt`, the time this run read it; the data is whatever the board published at that moment.

**Modifications made here.** Rows are filtered to companies, renamed to the columns above, dates converted to `YYYY-MM-DD`, Oregon's one-row-per-license-class records merged into one row per license (classes sorted by registration date and class, joined; bond, insurer and expiration taken from the earliest class that has a bond), and address lines that are not address-shaped, person-shaped or c/o withheld, and specialty texts with contact or first-name shapes dropped. Nothing is added from other sources.

This actor is independent and is not affiliated with or endorsed by Washington L\&I or the Oregon CCB. Data is provided as published, without warranty; check a license with the board before relying on it.

Only the official Socrata open-data API is used, one request at a time with a pause of more than one second between requests. No login, no web-page scraping.

### Privacy

- Only company-level columns are requested. Washington's principal name and Oregon's responsible managing individual (RMI) name and fax number are never downloaded, and neither are Washington's principal and authorized-signer datasets.
- A row is returned only when the business name, exactly as filed, passes an allowlist test:
  - Every character is plain ASCII (any other character, even one that looks like a Latin letter, fails the name): letters, digits and spaces plus `& , . ' -`. A dash, dot or apostrophe sits only inside a word (a dot may also end an abbreviation such as 'INC.', and an apostrophe may follow a final S, as in "RESIDENTS'"). '&' is spaced on both sides ('R & R') or glued only where one side is a single letter or digit ('A\&B', 'AT\&T'). A comma is allowed only right before the closing legal form ('ACME, LLC').
  - The last word is a legal form or organisation word (LLC, INC, CORP, LTD, LP, LLP, PLLC, PC, CO, NA, COMPANY, CORPORATION, ASSOCIATION, BANK, ...). A bare CO counts only on a Washington row typed as a company; Oregon needs 'CO.' or '& CO'. No legal form appears earlier in the name, except a word like COMPANY or CORPORATION directly before the last one ('ACME COMPANY LLC'), and CO or NA never does.
  - It holds no joiner, capacity, officer, fiduciary, trust or title word: DBA, AKA, FKA, TA, OBO, WITH, OR, BY, VIA, FOR, AS, ET, OWNER, OPERATING, TRADING, ATTN, CARE, TRUST, TRUSTEE, TR, TRS, ESTATE, EST, FAMILY, LIVING, PRESIDENT, MEMBER, MANAGER, AGENT, OFFICER, PARTNER, INDIVIDUAL, EXECUTOR, GUARDIAN, MR, MRS, DR, MD, ESQ, JR, SR and similar. These joiners also fail when spelled with dots, spaces, dashes, quotes or '&' inside them ('D.BA', 'D B A', 'D\&B\&A', 'O BO', 'W ITH').
  - It has no two single letters in a row ('C.O.', 'J P') and no run of 7 or more digits.
  - When it joins parts with '&' or AND, no part reads as a person's name, and the last part is itself a company.
- Washington rows whose business type is `Individual` are always dropped, whatever the name says; Oregon publishes no business type, so there only this name test applies. Nothing is cut out of a name: a name that fails drops the whole row (a sole proprietor's business address is a person's address). Surety and insurer names follow the same rule and are blank when they fail.
- This is default-deny, so it also withholds real companies, for example 'TA CONSTRUCTION LLC', 'BUILT BY DESIGN LLC', 'ST. JAMES BAKERY LLC', 'GARAGE DOOR MD LLC', 'BANK OF AMERICA' (legal form not last) and any name with an accent or symbol. It does not detect a person's name inside a legal name that passes: a business named after its owner (for example 'EXAMPLE SMITH PAINTING LLC') is that organisation's legal name and is returned. Likewise, a plain-text name that strings a person and a firm together without any marker cannot be told apart from a registered business name, and is returned.
- `specialties` and `licenseType` are publisher text; a part is left out unless it is plain ASCII (after Unicode normalisation) and has no '@', no run of 7 or more digits (ignoring every character that is not a letter or digit) and no common first name.
- The address is the one the company filed with the board. For a small company that can be a home or apartment address; it is shown because the company filed it as its business address. An address is published only when it is address-shaped: the first line starts with a street number or a PO, HC or RR box, and after its last street-type word (ST, AVE, RD, WAY, ...), box number or route number holds only unit, suite, building or direction words and numbers (a line with no street-type word may hold only numbers and directions); any second line holds only such words and numbers (a short unit letter code after a unit word is allowed, but never a common first name such as 'STE AMY'); and every character is a plain letter, digit or address punctuation. An address with a line that hands mail to someone else (c/o, attn, care of), reads as a person's name, holds a phone-like run of 7 or more digits joined only by separators, or has any other text is withheld with its city, region and ZIP. This also withholds some real addresses (80 of 5,337, or 1.5%, in a 2026-10-19 sample), mostly ones without a street-type word, for example '401 BROADWAY STE 100'. E-mail-shaped values are never passed on.
- No skip tracing and no contact enrichment. Calls and texts to listed phone numbers are the buyer's responsibility under TCPA and do-not-call rules.

### Use with AI agents (MCP)

The actor runs with limited permissions, charges exactly once per returned row plus the tiny per-run start event, and returns a fixed column set described by its dataset schema. That makes it straightforward for agents to call through the Apify MCP server and to budget.

### FAQ

**Why is a contractor I expected missing?** It is a Washington sole proprietor, its name does not end in a legal form or organisation word, its name has a symbol, a non-ASCII character, a listed word (dba, by, for, trust, estate, member, md, ...), CO before the last word or two single letters in a row, or (Oregon) its license is not active. That loses some real companies rather than ever returning a person's name.

**Can I look up one license?** Not in this version; it is a date-window feed. Use `cities`, `zipPrefix` or `specialtyContains` to narrow it.

**How do I schedule it?** Create a daily schedule with `daysBack` around 7. Changes-only mode means the overlap costs nothing.

**How do I get CSV or Excel?** Open the run's dataset and choose the export format, or call the dataset items API with `format=csv`.

### Related actors

- New Business Filings by State: Daily LLC & Corporation Feed (No Personal Data)
- UCC Lien Filings Feed: New Liens by State (Entity-only)

# Actor input Schema

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

Licensing boards to read.

## `events` (type: `array`):

licensed: license effective (WA) or first registered (OR) in the window. suspended: suspension date in the window (Washington only; Oregon publishes active licenses only).

## `daysBack` (type: `integer`):

Event-date window counted back from today.

## `sinceDate` (type: `string`):

Optional start date (YYYY-MM-DD); overrides Days back (never more than 90 days back).

## `specialtyContains` (type: `string`):

Keep only licenses whose specialty or endorsement contains this text, e.g. ELECTRICAL or Residential (case-insensitive).

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

Keep only these cities (exact, case-insensitive).

## `zipPrefix` (type: `string`):

Keep only postal codes starting with these digits, e.g. 981.

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

Remembers what this input already returned and skips it, so scheduled runs never pay twice for the same license.

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

Upper bound on rows returned (and charged) per run, newest first.

## Actor input object example

```json
{
  "states": [
    "WA",
    "OR"
  ],
  "events": [
    "licensed"
  ],
  "daysBack": 7,
  "onlyNewSinceLastRun": true,
  "maxItems": 200
}
```

# Actor output Schema

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

One company-only row per licence event, in the Overview column order.

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

Per-source status, freshness, row counts, notes and stop reason (never 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 = {
    "states": [
        "WA",
        "OR"
    ],
    "events": [
        "licensed"
    ],
    "daysBack": 7,
    "onlyNewSinceLastRun": true,
    "maxItems": 200
};

// Run the Actor and wait for it to finish
const run = await client.actor("quaky_ozone/contractor-license-feed").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",
    ],
    "events": ["licensed"],
    "daysBack": 7,
    "onlyNewSinceLastRun": True,
    "maxItems": 200,
}

# Run the Actor and wait for it to finish
run = client.actor("quaky_ozone/contractor-license-feed").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"
  ],
  "events": [
    "licensed"
  ],
  "daysBack": 7,
  "onlyNewSinceLastRun": true,
  "maxItems": 200
}' |
apify call quaky_ozone/contractor-license-feed --silent --output-dataset

```

## MCP server setup

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

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/MhPNQCRsLMDJT1SKP/builds/FfqPytpbk3pXTGrKD/openapi.json
