# CA DIR Registrations: Car Wash, Janitorial, Garment, Farm Labor (`overlookdata/ca-dir-registration-search`) Actor

Every California DIR labor registration in one deduplicated dataset: car wash, farm labor contractor, garment, janitorial, talent agency, and entertainment permits for minors, one record per registrant with all registrations, statuses, dates, addresses, and workers' comp carriers.

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

## Pricing

from $1.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 DIR Labor Registrations (Car Wash, Farm Labor, Garment, Janitorial, Talent Agency, Entertainment Permits)

Every California labor registration the Department of Industrial Relations
publishes, in one clean dataset: car wash, farm labor contractor, garment
manufacturer and contractor, janitorial (property service), talent agency, and
entertainment permits to employ minors. About 57,500 registration records
across roughly 27,000 registrants (a full sweep on 2026-08-22), with
registration numbers, statuses, effective and expiration dates, business
addresses, and workers' compensation carriers.

DIR publishes this only through a search form that requires a registration type
plus a second criterion on every query, and it caps a response at 15 MB.
Answering "is this contractor currently registered, and for what" means running
searches by hand, one type at a time. This actor runs the whole sweep, verifies
its own coverage with a second independent sweep, and returns one record per
registrant with every registration attached.

### Who this is for

**Anyone with a statutory duty to verify that a labor contractor is registered
before doing business with them.** California requires registration for each of
these industries, and it requires the people who hire them to check:

- **Garment manufacturing and contracting.** Every person engaged in the
  business of garment manufacturing must register with the Labor Commissioner
  (Labor Code 2675), and a manufacturer who contracts with an unregistered
  contractor is deemed an employer and jointly liable (Labor Code 2677).
- **Farm labor contractors.** No one may act as a farm labor contractor without
  a license from the Labor Commissioner (Labor Code 1683), and the contractor
  must hand the grower a copy of the current license before contracting, which
  the grower keeps for three years (Labor Code 1695.7).
- **Janitorial / property service employers.** Every janitorial employer must
  register annually under the Property Service Workers Protection Act (Labor
  Code 1423), and anyone who contracts with an employer lacking a current and
  valid registration "as displayed on the online registration database" is
  subject to penalties (Labor Code 1432).
- **Car wash businesses.** Every car washing and polishing employer must
  register annually (Labor Code 2054) and post a surety bond of at least
  $150,000 to do so (Labor Code 2055).
- **Talent agencies.** No one may carry on the occupation of a talent agency
  without a license from the Labor Commissioner, and the license number must
  appear in any advertisement soliciting talent (Labor Code 1700.5).
- **Entertainment permits to employ minors.** Employing a minor in the
  entertainment industry requires the Labor Commissioner's written consent in
  the form of a permit (Labor Code 1308.5).

Section numbers were checked against the California Legislative Information
site on 2026-08-22. This is a description of the registry, not legal advice.

Practical uses:

- Screen vendors and subcontractors against the live registry before onboarding.
- Keep a compliance database current instead of re-searching before every job.
- Monitor expiration dates and workers' comp coverage across a supplier list.
- Research enforcement and registration trends across an industry.

### What you get

One record per registrant (default), with every registration it holds:

```json
{
  "registrant_key": "001t000000BPBQRAA5",
  "name": "Torrance Carwash, Inc.",
  "legal_entity_name": "Torrance Car Wash, Inc.",
  "registration_types": ["Car Wash"],
  "current_registrations": [
    {
      "registration_id": "a1Rcs000006ZqhpEAC",
      "record_name": "LR-1001239196",
      "registration_number": "CW-LR-1000877861",
      "registration_type": "Car Wash",
      "registration_type_label": "Car Wash",
      "service_type": "Renewal",
      "status": "Renewed",
      "effective_date": "2025-03-31",
      "expiration_date": "2026-03-31",
      "submitted_date": "2025-03-20",
      "legal_entity_name": "Torrance Car Wash, Inc.",
      "address_street": "2476 Sepulveda Blvd",
      "address_city": "Torrance",
      "address_state": "CA",
      "address_zip": "90501",
      "workers_comp_carrier": "Amtrust North America",
      "workers_comp_selection": "Insured by carrier",
      "workers_comp_expiration_date": "2025-07-01",
      "source_url": "https://cadir.my.salesforce-sites.com/RegistrationSearch/RegistrationDetails?id=a1Rcs000006ZqhpEAC"
    }
  ],
  "registrations": ["... every registration and renewal on file, newest first"],
  "registration_count": 7,
  "address_street": "2476 Sepulveda Blvd",
  "address_city": "Torrance",
  "address_state": "CA",
  "address_zip": "90501",
  "has_active_registration": true,
  "details": null,
  "scraped_at": "2026-08-22T21:00:00+00:00"
}
```

A registrant with seven annual renewals is one record with seven registrations
listed, not seven rows. `current_registrations` holds the newest registration in
each type, which is the one you check when you are verifying standing today.

There is no phone number and no email address in this data, because DIR does not
publish either one in this registry. What it does publish is the legal entity
name, the physical business address, the registration number and status, the
dates, and the workers' compensation carrier.

### Input

Run it with no input to sweep all six registration types. Or narrow it.

| Field | Type | Default | Description |
|---|---|---|---|
| `registrationTypes` | array of strings | `[]` (all six) | `Car Wash`, `Farm Labor Contractor`, `Garment Manufacturers and Contractors`, `Janitorial`, `Talent Agency`, `Permit to Employ Minors`. |
| `statuses` | array of strings | `[]` (all) | Keep only these DIR statuses: `Approved`, `Renewed`, `Temporary Approved`, `Expired`, `Closed - Renewed`, `Closed - Denied`, `Closed - Revoked`, `Closed - Suspended`, `Closed_Suspended`. |
| `outputMode` | `registrants` or `registrations` | `registrants` | Dataset shape, see below. |
| `includeDetails` | boolean | `false` | Also fetch each record's DIR detail page (DBA names, branch locations, bond history). Multiplies the request count, so leave it off for a full sweep. |
| `verifyCoverage` | boolean | `true` | Run a second sweep by expiration date and compare record IDs. Result goes to the `VERIFICATION` key-value record. |
| `firstYear` | integer | `1990` | Lower bound of the date slices. Raise it to sweep only recent years. |
| `requestDelayMs` | integer | `250` | Politeness delay per worker after each request. |
| `maxConcurrency` | integer | `2` | Maximum simultaneous requests. |

Unknown registration types or statuses fail fast with a clear error instead of
silently sweeping nothing.

#### Example runs

Quick test, one type, recent years only (finishes in seconds):

```json
{ "registrationTypes": ["Car Wash"], "firstYear": 2024 }
```

Everyone currently registered as a garment manufacturer or contractor:

```json
{ "registrationTypes": ["Garment Manufacturers and Contractors"], "statuses": ["Approved", "Renewed", "Temporary Approved"] }
```

Full sweep, all six types, all history (a few minutes):

```json
{}
```

Flat registration rows for a warehouse load, with DBA names and branch
addresses:

```json
{ "outputMode": "registrations", "includeDetails": true, "firstYear": 2020 }
```

### Output modes

**`registrants`** (default): one record per registrant, keyed on DIR's own
account ID, with `registrations` (all of them, newest first),
`current_registrations` (the newest per type), and `has_active_registration`.

**`registrations`**: one flat record per registration, exactly as DIR stores it,
with the registrant's account ID and name on every row. Use this when you want a
row per renewal, or when you are loading into a table that does not want nested
arrays.

### Data quality and the verification summary

- **The sweep verifies itself.** Records are collected in effective-date slices,
  then collected again in expiration-date slices, and the two sets of record IDs
  are compared per type inside the window both sweeps could see. The comparison
  is written to the `VERIFICATION` record in the run's key-value store: per-type
  counts, slices fetched, slices split, any failing slice, and the IDs found by
  one sweep but not the other. Records found only by the second sweep are
  merged into the dataset (`merged_from_check`), so the output is the union of
  both passes. Expect `match: true` on most runs; a handful of IDs under
  `missing_from_check` is usually DIR data changing between the two passes, and
  an ID under `missing_from_primary` is usually a DIR data-entry error (one
  farm labor registration carries an effective date in the year 2120, which
  only the expiration-date pass can see).
- **A slice that is too large splits itself.** DIR caps a response at 15 MB. A
  slice that trips the cap is halved and re-fetched, down to single days, and the
  split count is reported. A slice that still fails is recorded as an error and
  the rest of the sweep continues, rather than aborting the run.
- **Dates are converted.** DIR returns dates as epoch milliseconds; every date in
  the output is an ISO `YYYY-MM-DD` string in UTC.
- **Statuses are DIR's own raw values,** not normalized. `Renewed`,
  `Approved`, and `Temporary Approved` are the standings that count as active;
  `has_active_registration` also requires an expiration date on or after the
  scrape date.
- **Registrants are matched on DIR's account ID.** Where DIR publishes no account
  ID, the actor falls back to a SHA-1 of the normalized legal entity name and
  address, so those records still collapse instead of duplicating.
- **Sole proprietors appear under personal names,** and the physical business
  address can be a home address, especially in janitorial and farm labor. This is
  what the state publishes so that the parties required to verify registration
  can do so. Treat it accordingly, and do not treat this dataset as a
  consumer-marketing list.

### Politeness

The actor is deliberately gentle: 2 concurrent requests by default, a 250 ms
delay per worker, exponential backoff on server errors and timeouts, and a
User-Agent that identifies the actor. Detail-page fetching is off by default so
a normal run makes a few hundred requests, not tens of thousands.

### Source

[California Department of Industrial Relations, Division of Labor Standards
Enforcement, Registration Search](https://cadir.my.salesforce-sites.com/RegistrationSearch).
This actor is not affiliated with or endorsed by DIR or the State of California.
Data accuracy is DIR's own; DIR's site disclaims accuracy and directs users to
verify against the statute. Each record carries the timestamp it was scraped.

# Actor input Schema

## `registrationTypes` (type: `array`):

Pick the registration programs to sweep. Leave empty to sweep all six.

## `statuses` (type: `array`):

Keep only registrations with these statuses. Leave empty to keep every status, including expired and closed history. These are DIR's own stored status values.

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

One record per registrant with all of its registrations attached, or one flat record per individual registration.

## `includeDetails` (type: `boolean`):

Fetch the DIR detail page for the newest registration of every registrant (or for every registration in registrations mode). This multiplies the request count by roughly the number of output records and makes a full sweep much slower, so leave it off unless you need DBA names, branch addresses, or bond history.

## `verifyCoverage` (type: `boolean`):

Sweep a second time by expiration date and compare the record IDs against the effective-date sweep. The comparison is written to the VERIFICATION record in the default key-value store.

## `firstYear` (type: `integer`):

Lower bound of the date slices. DIR's oldest registrations start in 1990; raise this to sweep only recent years.

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

Politeness delay each worker waits after a request.

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

Maximum number of simultaneous requests to DIR.

## Actor input object example

```json
{
  "registrationTypes": [
    "Car Wash"
  ],
  "statuses": [],
  "outputMode": "registrants",
  "includeDetails": false,
  "verifyCoverage": true,
  "firstYear": 1990,
  "requestDelayMs": 250,
  "maxConcurrency": 2
}
```

# Actor output Schema

## `registrants` (type: `string`):

No description

## `verification` (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 = {
    "registrationTypes": [
        "Car Wash"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("overlookdata/ca-dir-registration-search").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 = { "registrationTypes": ["Car Wash"] }

# Run the Actor and wait for it to finish
run = client.actor("overlookdata/ca-dir-registration-search").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 '{
  "registrationTypes": [
    "Car Wash"
  ]
}' |
apify call overlookdata/ca-dir-registration-search --silent --output-dataset

```

## MCP server setup

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

```

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/fIQKI2VzIN93Ak1Uc/builds/Udb4xQPPlLDw4ZMNz/openapi.json
