# Contractor Licence Check (OR & WA) (`opendatatools/contractor-license-lookup`) Actor

Verifies Oregon CCB and Washington L\&I contractor licences from official daily open data: active status, bond and insurance with expiry dates, and whether each is valid today.

- **URL**: https://apify.com/opendatatools/contractor-license-lookup.md
- **Developed by:** [Nico Poland](https://apify.com/opendatatools) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 license record returneds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Contractor Licence Check (OR & WA)

Verifies Oregon CCB and Washington L\&I contractor licences from official
daily open data: active status, bond and insurance with expiry dates, and
whether each is valid today.

### What it does

Give it licence numbers or business names and it answers the question you
actually have: **is this contractor licensed, active, bonded and insured
right now?**

Both states publish their licensing registers as open data, refreshed
daily. This Actor reads those registers directly (Oregon's CCB file, and
Washington's L\&I licence, bond and insurance files joined on the licence
number) and computes three plain booleans:

| Field | Means |
|---|---|
| `is_active` | The licence is usable today, by the state's own status. |
| `is_bonded` | A surety bond is in force today. |
| `is_insured` | Liability cover is in force today. |

Both states publish expiry dates and leave the arithmetic to you. A licence
can look perfectly healthy while the bond behind it lapsed three weeks ago.

**Two things this surfaces that the state portals do not:**

1. **Bond impairment** (Washington). Claims have been paid out against the
   contractor's bond. It stays true after they file a replacement bond,
   because it is a fact about the contractor rather than the paperwork,
   so it is computed from the whole bond history, not the current record.
2. **Continuous bonds.** Washington records an open-ended bond by writing
   text where a date would go. Read as a date that is simply missing, and
   45,078 of the 177,029 bond records look like "cover unknown" when they
   are in fact the strongest cover there is.

**What it does not do.** It is a verification tool, not a directory. You
look up contractors you already have, by licence number or business name.
There is no way to ask "which licences does this person hold", and no
bulk export by city or trade.

### Input

Give `licenseNumbers`, `businessNames`, or both.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `licenseNumbers` | array of strings | one of | n/a | Up to 100. OR numbers are digits (`260124`); WA are alphanumeric (`247PRPM916QW`). |
| `businessNames` | array of strings | one of | n/a | Up to 50. Partial names match. |
| `states` | array | no | `["OR","WA"]` | Which registers to search. |
| `maxResults` | integer | no | `500` | Stop after this many records. |
| `failOnEmpty` | boolean | no | `false` | Operator health check; leave off. |

```json
{
  "licenseNumbers": ["247PRPM916QW", "260124"],
  "businessNames": ["ACME CONSTRUCTION"],
  "states": ["OR", "WA"]
}
```

### Output

One row per licence record. A real Washington row from a real run:

```json
{
  "state": "WA",
  "license_number": "247PRPM916QW",
  "business_name": "24-7 PROPERTY MAINTENANCE LLC",
  "status": "ACTIVE",
  "is_active": true,
  "is_bonded": true,
  "is_insured": true,
  "license_type_description": "CONSTRUCTION CONTRACTOR",
  "license_expires_on": "2027-12-29",
  "bond_company": "Travelers Casualty & Surety Co of America",
  "bond_amount": 30000.0,
  "bond_is_continuous": true,
  "bond_impaired": true,
  "bond_impaired_on": "2026-03-10",
  "insurance_company": "FEDERATED RESERVE INSURANCE CO",
  "insurance_amount": 1000000.0,
  "insurance_expires_on": "2027-06-17",
  "principal_name": "THOMPSON, FRANK EDWARD",
  "phone": "(509) 826-6895",
  "record_url": "https://secure.lni.wa.gov/verify/Detail.aspx?UBI=602874170"
}
```

Read that row: currently licensed, bonded and insured, **but a previous
bond was impaired in March.** That is the contractor you want to ask a
question or two before signing.

`is_bonded` and `is_insured` are **absent**, not `false`, when the state
records no cover at all. "Expired" and "never recorded" are different
facts and you should be able to act on the difference.

Full field documentation is on the Output tab (37 fields, three views).

### Pricing

Pay per event. You are charged **$0.004 per licence record returned**, plus
Apify's standard $0.00005 Actor start event.

**Example:** verifying 25 subcontractors = 25 × $0.004 + $0.00005 =
**$0.10**.

You are never charged for a "not found" explanation, for failed runs, or
for anything beyond your `maxResults`.

### Limits and refresh cadence

- **Coverage:** Oregon and Washington only. California is deliberately not
  included: CSLB is well served by existing tools and publishes no
  comparable bulk feed.
- **Freshness:** both registers are refreshed daily by the states.
  Responses are cached for 6 hours.
- **Oregon publishes only current licences.** A contractor whose licence
  has lapsed simply is not in the file, so "not found" in Oregon can mean
  "not licensed today". The Actor says so rather than returning silence.
  Washington publishes expired and suspended licences with their status.
- **`bond_impaired` is Washington-only.** Oregon publishes no equivalent,
  so its absence in an Oregon row means "not published", not "clean".
- **This is a record check, not an endorsement.** It reports what the
  states publish. It cannot tell you whether the work will be any good.
- **Verification only:** no reverse lookup from a person to their
  licences, and no bulk export by city or trade.

### Attribution and privacy

- Oregon: [CCB Active Licenses](https://data.oregon.gov/d/g77e-6bhs),
  Oregon Construction Contractors Board, public domain.
- Washington: [L\&I Contractor License
  Data](https://data.wa.gov/d/m8qx-ubtq), Washington State Department of
  Labor & Industries, Open Data Commons PDDL.

Source, licence and retrieval time are stamped on every row.

These registers exist so the public can check a contractor, and both
states publish the responsible individual's name for that purpose, so
`principal_name` is included. What this Actor deliberately does **not**
do is let you search from a person to their businesses, or export contact
lists by area or trade. It answers questions about a named contractor;
it is not a lead list.

### Changelog

See the Changelog tab.

### Issues

Report problems on the Issues tab. Please include the licence number and
state. Breakage is normally fixed within one working day.

# Actor input Schema

## `licenseNumbers` (type: `array`):

Contractor licence numbers to verify. Oregon CCB numbers are digits (e.g. 260124); Washington L\&I numbers are alphanumeric (e.g. 247PRPM916QW).

## `businessNames` (type: `array`):

Business names to search for. Partial names match, so "ACME" finds "ACME CONSTRUCTION LLC".

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

Which registers to search. Oregon's file lists only currently-licensed contractors; Washington's includes expired and suspended licences with their status.

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

Stop after this many licence records. A broad name search can match many contractors. You are never charged for more than you receive.

## `failOnEmpty` (type: `boolean`):

Leave this off for normal use. The daily monitor run turns it on so that 'succeeded but returned nothing' shows up as a failure instead of passing silently.

## Actor input object example

```json
{
  "licenseNumbers": [
    "247PRPM916QW",
    "260124"
  ],
  "businessNames": [
    "ACME CONSTRUCTION"
  ],
  "states": [
    "WA"
  ],
  "maxResults": 500,
  "failOnEmpty": false
}
```

# Actor output Schema

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

Every row this run produced, in the default dataset.

# 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 = {
    "licenseNumbers": [],
    "businessNames": [
        "24-7 PROPERTY MAINTENANCE"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("opendatatools/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 = {
    "licenseNumbers": [],
    "businessNames": ["24-7 PROPERTY MAINTENANCE"],
}

# Run the Actor and wait for it to finish
run = client.actor("opendatatools/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 '{
  "licenseNumbers": [],
  "businessNames": [
    "24-7 PROPERTY MAINTENANCE"
  ]
}' |
apify call opendatatools/contractor-license-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,opendatatools/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/rdqXiHJbI96mlhsN7/builds/gcZvzMScxZV735GGy/openapi.json
