# OSHA Enforcement Monitor — New Inspections, No Login, $20/1k (`outstanding_vegetable/osha-enforcement-monitor`) Actor

Watch companies, states or NAICS codes on osha.gov and get only NEW or UPDATED inspections since the last run, with citations, violation counts by type and initial/current penalties. Weekly schedule. No login or API key. MCP-ready. $20 per 1,000 alerts.

- **URL**: https://apify.com/outstanding\_vegetable/osha-enforcement-monitor.md
- **Developed by:** [Peter Skotte](https://apify.com/outstanding_vegetable) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 inspection alerts

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

## OSHA Enforcement Monitor

Watch companies, states or industries for **new OSHA inspections** and for **citation and penalty changes** on inspections you already know about. Schedule it weekly and every run returns only what changed since the last one, straight from osha.gov's public enforcement records. No login, no API key.

Each alert carries the inspection id, establishment, site address, NAICS, open/close dates, inspection type and scope, case status, violations by type (serious / willful / repeat / other), initial and current penalties and the individual citation items.

### Who uses this

- **Safety and EHS consultants** — get notified when a client, prospect or competitor site is inspected and when citations are issued.
- **Insurers and underwriters** — track enforcement activity on insured accounts and in high-risk NAICS codes.
- **Corporate EHS teams** — monitor your own establishments across states, including state-plan states.
- **Law firms and labor organizations** — follow willful/repeat citations and penalty settlements.
- **Analysts and journalists** — watch a whole industry (NAICS) or state for enforcement trends.

### Input

| Field | Default | Meaning |
|---|---|---|
| `companyNames` | `["Amazon", "Tesla"]` | Establishment names (substring match on osha.gov). Clear to watch states or industries only. |
| `states` | `[]` | Two-letter state codes. Narrows companies/NAICS to those states; alone, watches every inspection in each state. |
| `naicsCodes` | `[]` | 2–6 digit NAICS codes, e.g. `493110` (warehousing) or `23` (all construction). |
| `lookbackDays` | `30` | Inspections opened within this window are scanned. Keep it longer than your schedule interval. |
| `includeCitations` | `true` | Fetch each emitted inspection's detail page (violations, penalties, citations, address). Also enables penalty-change detection. |
| `maxNewPerQuery` | `5` | Alerts per company/state/NAICS query per run; the rest arrive on later runs. |
| `maxItems` | `10` | Total alerts per run. |
| `firstRunMode` | `emitAll` | `emitAll` emits everything in the window on the first run; `baseline` records it silently so only future inspections are emitted. |
| `webhookUrl` | `""` | POST a run summary plus up to 50 alerts here after each run. |
| `monitorId` | `default` | Name of the watch list. Each id has its own memory. |
| `dolApiKey` | `""` | Optional DOL Open Data API key (see below). |

#### Query semantics

- `companyNames` and `naicsCodes` are the things you watch; `states` restricts them. `["Amazon"]` + `["TX","CA"]` = Amazon inspections in Texas and California.
- With no companies and no NAICS codes, each state in `states` is watched on its own (every inspection opened there).
- Every alert says which query matched it in `matchedQuery` (`company:Amazon state:TX`, `naics:493110`, `state:DE`).

### Output

One record per new or updated inspection:

```json
{
  "activityNr": "1915031.015",
  "establishmentName": "Mtn1 Amazon Fulfillment Center",
  "siteAddress": "1025 Boxwood Rd",
  "city": "Wilmington",
  "state": "DE",
  "zip": "19804",
  "naics": "454110",
  "openDate": "2026-09-03",
  "closeDate": null,
  "inspectionType": "Complaint",
  "scope": "Partial",
  "caseStatus": "OPEN",
  "office": "Wilmington Area Office",
  "violations": { "serious": 0, "willful": 0, "repeat": 0, "other": 0, "unclassified": 0, "total": 0 },
  "totalInitialPenalty": 0,
  "totalCurrentPenalty": 0,
  "citations": [],
  "oshaUrl": "https://www.osha.gov/ords/imis/establishment.inspection_detail?id=1915031.015",
  "matchedQuery": "company:Amazon",
  "changeType": "new",
  "firstSeenAt": "2026-09-28T14:02:11.000Z",
  "monitorId": "default"
}
```

`citations` lists each citation item with `citationId`, `citationType`, `standardCited`, `issuanceDate`, `abatementDueDate`, `initialPenalty`, `currentPenalty`, `ftaPenalty`, `contest` and `latestEvent` (e.g. `I - Informal Settlement`).

`changeType` is `new` for an inspection the monitor has not seen, or `updated` when its violation count, case status, close date or penalties changed since the last run. Penalty changes are detected by re-reading cited inspections inside the lookback window on every run (up to 300 per run), which is how settlements and contests show up.

The run's key-value store also gets a `SUMMARY` record: `{ newCount, updatedCount, scanned, seenTotal, baseline }`.

### Scheduling

Create an Apify schedule (weekly is a good default; OSHA posts inspections with a lag of a few days) with your watch list as input. Keep `lookbackDays` at least twice the schedule interval so nothing falls between runs. The monitor stores seen inspection ids and their citation fingerprint in a named key-value store `osha-monitor-<hash of monitorId>`; up to 50,000 ids are kept, oldest dropped first.

- **First run:** with `firstRunMode: "emitAll"` you get everything currently in the window (capped by `maxItems`), which doubles as a sample. With `"baseline"` the first run emits nothing and only future activity is reported.
- **Several watch lists:** give each its own `monitorId` (e.g. `clients`, `competitors`, `tx-construction`).
- **Reset:** delete the `osha-monitor-…` key-value store, or pick a new `monitorId`.

### Webhook

Set `webhookUrl` to receive a POST after every run:

```json
{ "monitorId": "default", "runAt": "…", "newCount": 3, "updatedCount": 1, "scanned": 61, "seenTotal": 120, "baseline": false, "inspections": [ …up to 50 records… ] }
```

Nothing is posted when there is no run (the actor did not start); a run with zero changes still posts a summary with an empty `inspections` array, so your receiver can tell "checked, nothing new" from "did not check".

### Data source: keyless vs. API-key mode

**Default (keyless):** inspections are listed from OSHA's public Establishment Search (`establishment.search`, for company names) and Inspections within Industry search (`industry.search`, for states and NAICS codes), and each emitted inspection's detail page supplies citations and penalties. These are the same pages a human uses on osha.gov; no key or login is needed.

**Optional API-key mode:** the U.S. Department of Labor also exposes the OSHA inspection table through its Open Data API v4 (free key from [dataportal.dol.gov](https://dataportal.dol.gov/registration)). If you supply `dolApiKey`, the monitor lists inspections through that API (filtered by open date, establishment name, state and NAICS) and still reads citation details from osha.gov. Any API error or unexpected response falls back to the keyless listing for that run, with a warning in the log. The API mode is offered as a convenience for users who already hold a key; the keyless path is the one this actor is tested against.

### Limits and notes

- Up to 1,000 inspections are scanned per query per run (10 pages of 100).
- Establishment name matching is OSHA's own substring match, so `"Amazon"` also returns `"Amazon Corporate Llc"` and `"Mtn1 Amazon Fulfillment Center"`.
- OSHA typically publishes an inspection within days of opening; citations appear weeks to months later, which is exactly what `changeType: "updated"` catches.
- Data covers federal OSHA and state-plan states (the `office` field tells you which).

### Pricing

$0.005 per run plus $0.02 per emitted inspection alert. A weekly monitor of a handful of companies typically costs well under $1 per month.

# Actor input Schema

## `companyNames` (type: `array`):

Establishment names to watch (OSHA substring match, e.g. "Amazon" matches "Amazon.Com Services Llc"). Clear this list to watch whole states or industries instead.

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

Two-letter US state codes. With companies or NAICS codes set, narrows them to these states; on its own, watches every inspection opened in each state.

## `naicsCodes` (type: `array`):

Industry codes to watch, 2 to 6 digits (e.g. 493110 warehousing, 23 all construction).

## `lookbackDays` (type: `integer`):

Only inspections opened within this many days are scanned. Keep it longer than your schedule interval.

## `includeCitations` (type: `boolean`):

Fetch each emitted inspection's detail page: violations by type, initial/current penalties, citation items, site address, case status. Also lets the monitor flag penalty changes on cases it already knows.

## `maxNewPerQuery` (type: `integer`):

Cap on new/updated inspections emitted per company, state or NAICS query in one run. The rest are emitted on later runs.

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

Total cap on inspections emitted in one run.

## `firstRunMode` (type: `string`):

emitAll: the first run emits everything currently in the window. baseline: the first run only records what exists, so later runs emit only what is new.

## `webhookUrl` (type: `string`):

Optional. After each run, POST a JSON summary plus up to 50 emitted inspections to this URL (Slack, Zapier, Make, your API).

## `monitorId` (type: `string`):

Name of this watch list. Each ID keeps its own memory of seen inspections, so you can run several monitors from one actor.

## `dolApiKey` (type: `string`):

Optional free key from dataportal.dol.gov. When set, inspections are listed through the DOL API v4 and osha.gov is used only for citation details; on any API error the monitor falls back to osha.gov automatically.

## Actor input object example

```json
{
  "companyNames": [
    "Amazon",
    "Tesla"
  ],
  "states": [],
  "naicsCodes": [],
  "lookbackDays": 30,
  "includeCitations": true,
  "maxNewPerQuery": 5,
  "maxItems": 10,
  "firstRunMode": "emitAll",
  "webhookUrl": "",
  "monitorId": "default"
}
```

# Actor output Schema

## `records` (type: `string`):

Dataset of new or updated OSHA inspections found in this run (JSON).

# 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 = {
    "companyNames": [
        "Amazon",
        "Tesla"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("outstanding_vegetable/osha-enforcement-monitor").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 = { "companyNames": [
        "Amazon",
        "Tesla",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("outstanding_vegetable/osha-enforcement-monitor").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 '{
  "companyNames": [
    "Amazon",
    "Tesla"
  ]
}' |
apify call outstanding_vegetable/osha-enforcement-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,outstanding_vegetable/osha-enforcement-monitor"
        }
    }
}
```

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/k9md7PLpR7xHmfmlG/builds/Y6sgDyit0pWJOd3C3/openapi.json
