# CA Public Works Apprenticeship Programs (DIR / DAS) (`overlookdata/ca-dir-apprenticeship-programs`) Actor

All California public works apprenticeship programs in one deduplicated dataset: every county, every craft, with contacts. Built for DAS 140 / DAS 142 compliance workflows.

- **URL**: https://apify.com/overlookdata/ca-dir-apprenticeship-programs.md
- **Developed by:** [Aaron Melton](https://apify.com/overlookdata) (community)
- **Categories:** Lead generation, Jobs, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 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?

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

## CA Public Works Apprenticeship Programs (DIR / DAS)

Every California public works apprenticeship program in one clean, deduplicated
dataset: all 58 counties, all apprenticeable crafts, with program names,
addresses, contact people, phone numbers, and emails.

The California Department of Industrial Relations (DIR) publishes this data
only through a one-county-at-a-time, one-craft-at-a-time search form. Answering
"which programs cover my counties and my trades" means running dozens of
searches by hand and reconciling duplicates yourself. This actor runs the full
sweep (about 1,600 county/craft combinations), verifies every page against the
site's own result counts, and merges the results into unique programs with
their complete county and trade coverage.

### Who this is for

**Contractors and labor compliance teams on California public works projects.**
State law requires contractors on public works to send contract award
information (form **DAS 140**) to the applicable apprenticeship committees and
to request dispatch of apprentices (form **DAS 142**). DIR's own guidance
points to this county/craft database as the way to find the programs and
contact information those forms go to. This actor gives you that entire
database as structured data instead of a search form:

- Build DAS 140 / DAS 142 mailing lists for all counties a project touches.
- Keep a compliance database current instead of re-searching before every bid.
- Feed program contacts into your CRM, compliance software, or spreadsheets.

Also useful for workforce development researchers, apprenticeship outreach
programs, and anyone studying California's construction trades.

### What you get

One record per unique program (default), with its full applicability:

```json
{
  "program_key": "sha1...",
  "name": "Northern California Area Plumbers, ... (J.A.T.C.)",
  "address_street": "1015 Yuba Street",
  "address_city": "Marysville",
  "address_state": "CA",
  "address_zip": "95901",
  "contact_person": "Beth Hammes",
  "contact_title": "Apprenticeship Coordinator",
  "phone": "(530) 673-8690",
  "email": "DASforms@lu228.org",
  "counties": ["Butte"],
  "county_codes": ["BUTTEC"],
  "trades": [{"code": "58", "labels": ["Plumbing & Pipefitting"]}],
  "pair_count": 1,
  "source_data_date": "08/18/2026",
  "scraped_at": "2026-08-18T21:00:00+00:00",
  "variants": []
}
```

A program that serves 12 counties in 2 trades is one record listing all 12
counties and both trades, not 24 duplicate rows.

### Input

Run it with no input to sweep the entire state. Or narrow the sweep.

In the Apify Console, counties and trades are **pick lists**: choose counties
and crafts by name ("Los Angeles", "Plumbing & Pipefitting") and leave either
list empty to search all of them. No codes to look up.

Calling the actor via API or JSON, the same fields take DIR's own codes:

| Field | Type | Default | Description |
|---|---|---|---|
| `counties` | array of strings | `[]` (all) | DIR county codes, e.g. `BUTTEC`, `LOS ANGELESE`. See the county code table below. |
| `trades` | array of strings | `[]` (all) | Two-digit craft codes, e.g. `58`. See the trade code table below. |
| `outputMode` | `programs` or `combinations` | `programs` | Dataset shape, see below. |
| `requestDelayMs` | integer | `500` | Politeness delay per worker after each request. |
| `maxConcurrency` | integer | `3` | Maximum simultaneous requests. |

Unknown county or trade codes fail fast with a clear error instead of silently
crawling nothing. County and craft lists are read live from the DIR form on
every run, so an empty-list sweep picks up new counties or crafts
automatically.

#### Example runs (JSON / API)

Console users just pick from the lists; these are the equivalent JSON inputs
for API calls, tasks, and schedules.

Quick test, one county and one craft (finishes in seconds):

```json
{ "counties": ["BUTTEC"], "trades": ["58"] }
```

Every program serving a project in Los Angeles County, all crafts:

```json
{ "counties": ["LOS ANGELESE"] }
```

Every electrical program in the state:

```json
{ "trades": ["22"] }
```

Full statewide sweep, all counties and all crafts (about 1,600 searches, a few
minutes):

```json
{ "counties": [], "trades": [] }
```

### Output modes

**`programs`** (default): one record per unique program, deduplicated across
all searches, with derived county and trade applicability (shown above).

**`combinations`**: one record per (county, craft, program) result, exactly as
the DIR search returns it, with the source URL for each row:

```json
{
  "county": "Butte",
  "county_code": "BUTTEC",
  "trade_code": "58",
  "trade_labels": ["Plumbing & Pipefitting"],
  "program_key": "sha1...",
  "name": "...",
  "phone": "...",
  "email": "...",
  "source_url": "https://www.dir.ca.gov/databases/das/results_pwadd.asp?varCounty=BUTTEC&varType=58"
}
```

### Tracking changes over time

Every record carries a `program_key`, a stable fingerprint of the program's
normalized name and address (case, spacing, and punctuation insensitive, so
DIR's own formatting quirks do not shift it). To monitor changes, schedule
the actor (monthly matches how often DIR's data moves) and diff runs on
`program_key` in your own pipeline:

- a key present in the new run but not the old one is a new program
- a key that disappeared is a program DIR no longer lists for your scope
- for matching keys, compare fields like `contact_person`, `phone`, `email`,
  `counties`, and `trades` to catch updates

Compare runs that used the same county and trade selection; narrowing the
input naturally removes programs from the output.

### Data quality and accuracy

- Every results page states "Search returned N sponsor(s)". The parser
  verifies its extracted count against N on every page. Mismatches are flagged
  in the data, never silently dropped. If DIR redesigns the site, the actor
  fails loudly instead of returning corrupted data.
- Every page's "Data is current as of MM/DD/YYYY" date is captured with each
  record (`source_data_date`), so you always know how fresh DIR's own data is.
- DIR's pages carry no program IDs and no geography. County and trade coverage
  is derived from which searches each program appears in, which is the only
  applicability information that exists.
- Programs are matched across searches by normalized name and address. If a
  program's contact details differ between result pages, the differing raw
  blocks are preserved in `variants` for review.
- Programs can be based outside California (for example, a Reno, Nevada JATC
  covers electrical work in Alpine County). Records are never assumed to have
  California addresses.
- Combinations with zero sponsors are recorded as legitimate empty results,
  distinct from fetch errors. Fetch errors are retried with backoff and
  reported per combination without aborting the sweep.

### Politeness

The actor is deliberately gentle with DIR's servers: 3 concurrent requests,
500 ms delay per worker, exponential backoff on errors, and an identifying
User-Agent. A full state sweep completes in a few minutes without stressing
the site.

### County codes

Needed only for API / JSON input; in the Console you pick counties by name.

| Code | County | | Code | County |
|---|---|---|---|---|
| `ALAMEDAA` | Alameda | | `ORANGEE` | Orange |
| `ALPINEC` | Alpine | | `PLACERC` | Placer |
| `AMADORC` | Amador | | `PLUMASC` | Plumas |
| `BUTTEC` | Butte | | `RIVERSIDEE` | Riverside |
| `CALAVERASC` | Calaveras | | `SACRAMENTOC` | Sacramento |
| `COLUSAC` | Colusa | | `SAN BENITOB` | San Benito |
| `CONTRA COSTAA` | Contra Costa | | `SAN BERNARDINOE` | San Bernardino |
| `DEL NORTEA` | Del Norte | | `SAN DIEGOG` | San Diego |
| `EL DORADOC` | El Dorado | | `SAN FRANCISCOA` | San Francisco |
| `FRESNOD` | Fresno | | `SAN JOAQUINC` | San Joaquin |
| `GLENNC` | Glenn | | `SAN LUIS OBISPOE` | San Luis Obispo |
| `HUMBOLDTA` | Humboldt | | `SAN MATEOB` | San Mateo |
| `IMPERIALG` | Imperial | | `SANTA BARBARAE` | Santa Barbara |
| `INYOD` | Inyo | | `SANTA CLARAB` | Santa Clara |
| `KERND` | Kern | | `SANTA CRUZB` | Santa Cruz |
| `KINGSD` | Kings | | `SHASTAC` | Shasta |
| `LAKEA` | Lake | | `SIERRAC` | Sierra |
| `LASSENC` | Lassen | | `SISKIYOUC` | Siskiyou |
| `LOS ANGELESE` | Los Angeles | | `SOLANOA` | Solano |
| `MADERAD` | Madera | | `SONOMAA` | Sonoma |
| `MARINA` | Marin | | `STANISLAUSC` | Stanislaus |
| `MARIPOSAD` | Mariposa | | `SUTTERC` | Sutter |
| `MENDOCINOA` | Mendocino | | `TEHAMAC` | Tehama |
| `MERCEDD` | Merced | | `TRINITYC` | Trinity |
| `MODOCC` | Modoc | | `TULARED` | Tulare |
| `MONOD` | Mono | | `TUOLUMNEC` | Tuolumne |
| `MONTEREYB` | Monterey | | `VENTURAE` | Ventura |
| `NAPAA` | Napa | | `YOLOC` | Yolo |
| `NEVADAC` | Nevada | | `YUBAC` | Yuba |

Codes are DIR's own form values (county name plus a trailing region letter).
Some contain spaces; pass them exactly as shown.

### Trade (craft) codes

Needed only for API / JSON input; in the Console you pick crafts by name.

| Code | Craft |
|---|---|
| `02` | Asbestos Workers |
| `07` | Boilermaker |
| `08` | Bricklayer |
| `10` | Carpentry |
| `12` | Carpet, Linoleum |
| `14` | Cement Masons |
| `21` | Sound / Communication Installer |
| `22` | Electrical & Electronic |
| `23` | Elevator Mechanic |
| `24` | Operating Engineer |
| `26` | Glazier & Glass Workers |
| `31` | Inspector / Tester |
| `32` | Iron & Steel Workers |
| `34` | Brick Tender, Laborers, Plaster Tender |
| `36` | Drywall / Lathers |
| `38` | Lineman |
| `48` | Millwright |
| `54` | Painting & Decoration |
| `55` | Drywall Finisher (Taper) |
| `57` | Plasterer |
| `58` | Plumbing & Pipefitting |
| `59` | Heating, Ventilation & Air Conditioning |
| `62` | Roofer |
| `63` | Fire Sprinkler Fitter |
| `64` | Sheet Metal |
| `65` | Teamster |
| `66` | Surveyor |
| `68` | Tile Layer/Setter |

Codes are DIR's two-digit craft codes. A few crafts share one code (for
example code `34` covers Brick Tender, Laborers, and Plaster Tender); records
list every label for a code.

### Source

[California Department of Industrial Relations, Division of Apprenticeship
Standards, Public Works apprenticeship program search](https://www.dir.ca.gov/databases/das/pwaddrstart.asp).
This actor is not affiliated with or endorsed by DIR. Data accuracy is
DIR's own; each record carries the site's stated data currency date.

# Actor input Schema

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

Pick the counties to search. Leave empty to search all 58 California counties.

## `trades` (type: `array`):

Pick the trades to search. Leave empty to search all apprenticeable crafts.

## `outputMode` (type: `string`):

One record per unique program (deduplicated, with derived county/trade coverage), or one record per county/craft/program search result with its source URL.

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

Politeness delay each worker waits after a request. The default is gentle on DIR's servers; a full state sweep still finishes in a few minutes.

## `maxConcurrency` (type: `integer`):

Maximum number of simultaneous requests to DIR.

## Actor input object example

```json
{
  "counties": [
    "BUTTEC"
  ],
  "trades": [
    "58"
  ],
  "outputMode": "programs",
  "requestDelayMs": 500,
  "maxConcurrency": 3
}
```

# Actor output Schema

## `programs` (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 = {
    "counties": [
        "BUTTEC"
    ],
    "trades": [
        "58"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("overlookdata/ca-dir-apprenticeship-programs").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 = {
    "counties": ["BUTTEC"],
    "trades": ["58"],
}

# Run the Actor and wait for it to finish
run = client.actor("overlookdata/ca-dir-apprenticeship-programs").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 '{
  "counties": [
    "BUTTEC"
  ],
  "trades": [
    "58"
  ]
}' |
apify call overlookdata/ca-dir-apprenticeship-programs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,overlookdata/ca-dir-apprenticeship-programs"
        }
    }
}

```

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/Cfvqra4YDOIE7sBVc/builds/g274I2Vx6xhx1h5Zj/openapi.json
