# Washington Prevailing Wage - Projects, Rates & Hours (`j0401/wa-prevailing-wage`) Actor

Washington L\&I public-works prevailing-wage records: 356,307 projects, 2,044,717 intent lines (filed rates and planned headcount) and 2,857,574 affidavit lines (hours actually paid). Profile a project or aggregate by agency, contractor, county, trade or year.

- **URL**: https://apify.com/j0401/wa-prevailing-wage.md
- **Developed by:** [Wenhao Yang](https://apify.com/j0401) (community)
- **Categories:** Business
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 50.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.03 / 1,000 washington prevailing-wage records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Washington Prevailing Wage - 356,307 Projects, Rates & Hours

Every public-works prevailing-wage filing Washington State's Department of
Labor & Industries publishes, in one schema:

- **356,307 filed projects** - awarding agency and category, prime contractor
  and UBI, contract number, type and amount, bid-due, award and completion
  dates, and the final apprentice / OMWBE utilisation figures.
- **2,044,717 intent lines** - the hourly rate, fringe rate and the **number of
  workers** a contractor intends to use, per project, trade and county. (The
  source column is literally "Number of Workers"; intent lines do not publish
  hours.)
- **2,857,574 affidavit lines** - the hours and rates actually paid, per
  project, trade, job classification and county.

Award dates run to **27 October 2026**; the newest awards are in the file.
Corpus counts are measured 10 November 2026 and move as L\&I publishes.

### Low cost

**From $0.00005 per record, down to $0.00003 at Gold.** Pay only for the
records you receive - the full 4.9 million wage lines and 356k projects stay
queryable, and you are never billed for the scan behind it.

### What you get

**Projects** carry the whole filed record: `agencyName`, `agencyCategory`
(School District, City, County, State Agency, Public Utility District, ...),
`contractorName`, `contractorUbi`, `projectName`, `contractNumber`,
`contractType`, `contractAmount`, `bidDueDate`, `awardDate`,
`workCompletedDate`, `federalFunded`, `housingAct`,
`apprenticeUtilizationRequired`, `apprenticeUtilization`, `omwbeUtilization`,
`journeylevelHours`, `apprenticeHours`, `totalHours`, `journeylevelWages`,
`apprenticeWages`, `totalWages`, `femaleHours`/`femaleUtilization`,
`minorityHours`/`minorityUtilization`, `veteranHours`/`veteranUtilization`,
`incentiveAmount`, `penaltyAmount`, `numberOfContractors`, `bondWaived`,
`retainageAmt`.

**Intent lines** carry `intentTrade`, `intentClassification`, `intentCounty`,
`intentHourlyRate`, `intentFringeRate`, `intentWorkers` (planned headcount -
the source's "Number of Workers", not hours).

**Affidavit lines** carry `affidavitTrade`, `affidavitClassification`,
`affidavitCounty`, `affidavitHourlyRate`, `affidavitFringeRate`,
`affidavitHours`.

Every record also carries `projectId`, `corpus`, `mode` and `totalMatched`.

This is the filed **project and wage-line register**. It is not L\&I's official
prevailing-wage rate determination schedule; the rates here are the ones a
contractor filed on a project.

### Search it

- **Contractor** - `contractor="kiewit"` (17 projects).
- **Agency** - `agency="SEATTLE, CITY OF"`, `"school district"`, `"county"`.
- **UBI** - `ubi="..."` for one firm's exact state business identifier. It is
  stored numerically, so 911 rows carry an 8-digit UBI (leading zero dropped).
- **Contract window** - `awardFrom="2026-01-01", awardTo="2026-12-31"`.
- **Dollar band** - `minAmount=1000000`.
- **One project's lines** - `corpus="affidavits", projectId="952101"` matches 1,468
  affidavit lines (the default `maxResults=50` delivers 50; raise it for all).
- **County and trade** - `corpus="affidavits", county="King", trade="Electrician"`
  (65,155 lines). County names are the file's own spelling - use `"King"`, not
  `"King County"`.
- **Rate floor** - `corpus="intents", minRate=80`.

### Example inputs

**Largest public-works contractors by project count**

```json
{ "mode": "aggregate", "corpus": "projects",
  "groupBy": "contractor", "metric": "count" }
```

That aggregation returns all **12,743** contractor groups (one billed record
each). `groupBy="agency"` is smaller at 2,825 groups.

**Federally funded projects awarded since 2026**

```json
{ "corpus": "projects", "federalFunded": true,
  "awardFrom": "2026-01-01", "maxResults": 100 }
```

**One project, with intent and affidavit totals**

```json
{ "mode": "profile", "projectId": "952101" }
```

**King County electrician affidavit lines**

```json
{ "corpus": "affidavits", "county": "King",
  "trade": "Electrician", "maxResults": 100 }
```

### Aggregate it

`mode="aggregate"` with `groupBy` and `metric`:

```json
{ "mode": "aggregate", "corpus": "affidavits",
  "groupBy": "county", "metric": "hours" }
```

- **Projects** - `groupBy` = `agency` (default), `contractor`,
  `agencyCategory`, `contractType`, `awardYear`; `metric` = `count`,
  `amount` (contract dollars), `hours`, `wages`.
- **Intent lines** - `groupBy` = `trade` (default), `county`,
  `classification`; `metric` = `count`, `workers` (summed headcount), `avgRate`.
- **Affidavit lines** - same dimensions; `metric` = `count`, `hours`
  (hours actually worked), `avgRate`.

Measured today: MCKINSTRY CO LLC 8,286 projects, JOHNSON CONTROLS FIRE
PRTTN LP 4,039, PACIFIC POWER GROUP LLC 3,266; BELLEVUE, CITY OF 6,628
projects, FISH AND WILDLIFE, WASH STATE DEPT OF 4,350; King County 869,286
affidavit lines and Pierce 271,775. Names are exactly as the file spells them.

### Coverage and boundaries

- **Washington State only.** L\&I publishes Washington public-works filings.
- **Row grain differs by corpus**: a project record is one filed project; an
  intent or affidavit record is one project / trade / classification / county
  line. The record's `corpus` field says which one you received.
- `intentId` and `affidavitId` are filing references, not unique line keys - the
  same reference appears on several trade/classification lines.
- The two line corpora carry **no agency or contractor columns** - that is how
  L\&I publishes them. Join them to projects by `projectId`, or use
  `mode="profile"` for one project's combined totals.
- The `trade` vocabularies differ between intents (260 values) and affidavits
  (1,697 values); they are not the same column renamed.
- Amounts are as filed: 42,826 small-works records carry a blank amount (the
  file has no zero-amount projects).
- The three checkbox filters (`federalFunded`, `housingAct`,
  `apprenticeUtilizationRequired`) treat `false` as "not marked": unchecked is
  published as null (76% of `housing_act`), so `false` matches false **or null**.
- **Billing**: every delivered record is one `wage-record`, including each
  aggregate row. On an aggregate row, `totalMatched` is the number of matching
  records, while `groupCount` is that group's count or sum. A `rows` query delivers at most 5,000 records (`maxResults`,
  itself capped there); an aggregate returns every group the dimension has
  (project `contractor` = 12,743 groups), so scope it with filters. When
  `maxResults` or the run-level charge cap stops a rows query, the dataset also
  carries one unbilled `_meta` row with `truncated`, `delivered`
  (a lower bound when `stopReason="charge-limit"`), `totalMatched` and `stopReason`.
- `mode="profile"` always reads the project corpus; it rejects a different
  `corpus` rather than silently ignoring it.
- `sourceUpdatedAt` is the source dataset's last update date from the portal
  metadata (newest award date if the metadata is unavailable).
- **About half of the line rows have no project record.** L\&I's project corpus
  covers intents filed on/after 1 July 2019 (its project ids start at 885,960),
  while the line files go back further: 1,102,450 of 2,044,717 intent lines
  (53.9%) and 1,344,424 of 2,857,574 affidavit lines (47.0%) point at an older
  id. `mode="profile"` still returns their line counts and hours/headcount, with
  `profileProjectFound=false` and empty project fields, rather than pretending
  the project does not exist.
- `housingAct=false` matches 355,107 of 356,307 projects because the unchecked
  state is published as null (269,726 rows) and `false` is treated as "not
  marked", i.e. false **or null**. The same rule applies to `federalFunded` and
  `apprenticeUtilizationRequired`.

### Source and licence

Washington State Department of Labor & Industries, published on the
[Washington open data portal](https://data.wa.gov/) (public records).

# Actor input Schema

## `mode` (type: `string`):

rows = records from the chosen corpus (default). aggregate = one row per group with a count, hours, headcount, amount, wages or average rate. profile = one project with its intent and affidavit totals (projectId required).

## `corpus` (type: `string`):

projects = filed public-works projects (agency, contractor, amounts). intents = wage rates and planned worker count, per project/trade/county. affidavits = hours and rates actually paid, per project/trade/classification/county.

## `projectId` (type: `string`):

L\&I project id, e.g. "952101". With corpus=intents or affidavits it returns that project's lines; with mode=profile it returns the project plus its exact intent and affidavit totals.

## `agency` (type: `string`):

Case-insensitive substring, e.g. "SEATTLE, CITY OF" or "school district". Projects corpus only.

## `contractor` (type: `string`):

Case-insensitive substring on the prime contractor name, e.g. "kiewit". Projects corpus only.

## `projectName` (type: `string`):

Case-insensitive substring on the project name. Projects corpus only.

## `contractNumber` (type: `string`):

Case-insensitive substring on the contract number. Projects corpus only.

## `ubi` (type: `string`):

Washington Unified Business Identifier of the prime contractor, as the file stores it. It is numeric, so 911 rows show an 8-digit UBI (a leading zero was dropped). Projects corpus only.

## `awardFrom` (type: `string`):

ISO date (YYYY-MM-DD). Projects corpus only.

## `awardTo` (type: `string`):

ISO date (YYYY-MM-DD). Projects corpus only.

## `minAmount` (type: `number`):

Projects corpus only.

## `maxAmount` (type: `number`):

Projects corpus only.

## `federalFunded` (type: `boolean`):

Projects corpus only. true = federally funded; false = not marked funded, which includes rows where the source leaves the box null (23,752 rows).

## `housingAct` (type: `boolean`):

Projects corpus only. true = Housing Act project; false = not marked Housing Act, including null (269,726 rows).

## `apprenticeUtilizationRequired` (type: `boolean`):

Projects corpus only. true = apprentice-utilisation requirements apply; false = not marked, including null (57,936 rows).

## `county` (type: `string`):

County name, e.g. "King". Intents and affidavits corpora.

## `trade` (type: `string`):

Case-insensitive substring on the trade name. Intents and affidavits corpora (their trade vocabularies differ: 260 vs 1,697 distinct values).

## `classification` (type: `string`):

Case-insensitive substring on the job classification. Intents and affidavits corpora.

## `minRate` (type: `number`):

Intents and affidavits corpora.

## `maxRate` (type: `number`):

Intents and affidavits corpora.

## `groupBy` (type: `string`):

Used only with mode=aggregate. Projects: agency (default), contractor, agencyCategory, contractType, awardYear. Intents/affidavits: trade (default), county, classification.

## `metric` (type: `string`):

count (default). projects also support amount (contract dollars), hours and wages; affidavits also support hours and avgRate; intent lines support workers (summed headcount - the source has no hours on intents) and avgRate.

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

How many records to deliver in rows mode (default 50, max 5000). Aggregate and profile modes ignore this.

## Actor input object example

```json
{
  "mode": "",
  "corpus": "projects",
  "groupBy": "",
  "metric": "",
  "maxResults": 50
}
```

# Actor output Schema

## `recordsUrl` (type: `string`):

Washington prevailing-wage records - as JSON

## `datasetUrl` (type: `string`):

No description

## `runUrl` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("j0401/wa-prevailing-wage").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("j0401/wa-prevailing-wage").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 '{}' |
apify call j0401/wa-prevailing-wage --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,j0401/wa-prevailing-wage"
        }
    }
}
```

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/jpqGbhaMVW1odIYjf/builds/NfYZBvXm2NeMjiQvP/openapi.json
