# WARN Layoff Notice Monitor — 15 States, No Login (`outstanding_vegetable/layoff-notice-monitor`) Actor

Watch WARN Act layoff and closure notices in 15 states (CA, NY, FL, WA, PA, VA and more) and get only NEW or updated notices since the last run: company, location, employees affected, notice and effective dates, notice PDF. Daily schedule. No login. MCP-ready. $20 per 1,000 alerts.

- **URL**: https://apify.com/outstanding\_vegetable/layoff-notice-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 layoff notices

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

## WARN Layoff Notice Monitor

Watches the official state WARN Act feeds (mass layoffs and plant closings) and emits **only the notices that are new or changed since the last run**. Point it at the states you sell into, schedule it daily or weekly, and get the leads pushed to a webhook.

WARN notices are the earliest public signal that a company is laying off 50+ people. Recruiters, staffing agencies, outplacement firms, workforce boards, lenders and journalists all use them, but every state publishes its own list in its own format. This actor normalizes 15 of them into one schema and remembers what it has already shown you.

### State coverage

| State | Source | Feed type | Update cadence | Notes |
|-------|--------|-----------|----------------|-------|
| CA | California EDD | XLSX report | Tuesdays and Thursdays | Notice, processed and effective dates, address, industry, closure vs layoff |
| TX | Texas Workforce Commission via data.texas.gov | Socrata JSON API | Batch updates, often weeks to months behind | TWC's own spreadsheet sits behind an AWS WAF challenge, so the open-data mirror is used; expect a lag |
| NY | New York DOL | Tableau Public dashboard exported as CSV | Continuous | Notice date, posted date, site address, county, reason, permanent/temporary |
| FL | Florida DEO (REACT) | HTML table, current year | Continuous | Address, industry, layoff date range |
| PA | Pennsylvania L\&I | HTML accordion grouped by month | Monthly grouping | Notice dates are month-level only (`noticeDate` = first of month, `postedDate` = end of month or today) |
| WA | Washington ESD | ASP.NET grid (16 per page) | Continuous | Layoff start date, permanent/temporary, PDF link |
| IN | Indiana DWD | HTML table | Continuous | NAICS and industry description, PDF link; fetched through Apify datacenter proxy (in.gov blocks direct cloud traffic) |
| MD | Maryland DLLR | HTML table | Continuous | Address, NAICS, county, effective date |
| VA | Virginia Works | HTML table | Continuous | Address, effective date, collective bargaining unit, PDF link |
| CO | Colorado CDLE | Google Sheet (current-year tab) exported as CSV | Continuous | Workforce area, begin/end dates, reason, occupations |
| UT | Utah DWS | HTML table | Continuous | Notice date, city, headcount only |
| AL | Alabama Department of Workforce | HTML table | Continuous | City, planned start date, closure vs layoff |
| DC | DC DOES | HTML table, current year | Continuous | Effective date may be a range or TBD |
| OR | Oregon HECC Rapid Response | HTML listing (25 per page) | Continuous | City column is the employer's address on file, not necessarily the affected site; PDF link |
| WI | Wisconsin DWD | Google Sheet exported as CSV | Continuous | County, NAICS description, layoff begin date, PDF link |

Not covered (checked September 2026): Illinois (DCEO site does not resolve), Massachusetts (report page removed), Michigan (JavaScript-only search), Ohio (client-rendered dashboard), New Jersey, North Carolina, Tennessee (PDF only), Georgia (list retired), Missouri (Incapsula wall), Arizona (search cannot be sorted by date). Ask if you need one of these.

### Output

One record per new or updated notice:

| Field | Description |
|-------|-------------|
| `state` | Two-letter state code |
| `company` | Employer name as published |
| `location` | City (or county / workforce area when the state publishes no city) |
| `county` | County when published |
| `address` | Site address when published |
| `employeesAffected` | Headcount, `null` when the state lists TBD |
| `noticeDate` | Date of the WARN notice |
| `postedDate` | Date the state posted or processed it (falls back to `noticeDate`); the lookback window is applied to this |
| `effectiveDate` | First layoff / closure date |
| `layoffType` | `closure`, `layoff` or `unknown` |
| `industry` | NAICS code or industry description when published |
| `unionInfo` | Collective bargaining unit when published (VA) |
| `noticeUrl` | Link to the notice PDF when the state provides one |
| `sourceUrl`, `sourceName` | Where the record came from |
| `changeType` | `new` on first sight, `updated` when the headcount or effective date changed |
| `previousEmployeesAffected` | Prior headcount on `updated` records |
| `firstSeenAt` | ISO timestamp of the run that first saw the notice |
| `noticeKey` | Stable key (state + company + notice date + site, or the state's own ID) |
| `monitorId` | The monitor this record belongs to |

Some states add `layoffDetail`, `effectiveDateEnd`, `effectiveDetail`, `reason`, `occupations` or `workforceArea`.

### Input

| Field | Default | Description |
|-------|---------|-------------|
| `states` | `["CA","TX","NY"]` | State codes to monitor (see table above) |
| `companyKeywords` | `[]` | Only notices whose company name contains one of these words |
| `minEmployees` | `0` | Skip notices below this headcount |
| `lookbackDays` | `14` | Only notices posted within this many days; `0` disables the window |
| `maxNewPerState` | `5` | Cap per state per run; the rest carry over to the next run |
| `maxItems` | `10` | Cap per run across all states |
| `firstRunMode` | `emitAll` | `baseline` makes the first run record everything and emit nothing |
| `webhookUrl` | `""` | POST a JSON summary with up to 50 notices after each run |
| `monitorId` | `default` | Each ID keeps its own memory of seen notices |
| `proxyConfiguration` | none | Optional; the feeds work without one |

### How the monitor works

1. Every run fetches the current list from each selected state.
2. Notices are filtered by the lookback window, headcount and keywords, then compared with the monitor's memory (a named key-value store, `warn-monitor-<hash of monitorId>`, capped at 50,000 keys).
3. Unseen notices are emitted as `new`; notices whose headcount or effective date changed are emitted as `updated`; everything else is skipped.
4. Emitted notices are remembered. Notices cut off by `maxNewPerState` / `maxItems` are not, so they appear on the next run.
5. If `webhookUrl` is set, one POST is sent per run with `{ monitorId, runAt, newCount, updatedCount, perState, errors, notices: [...] }`.

#### Recommended setup

- **Weekly digest**: schedule weekly, `lookbackDays: 14`, `maxNewPerState: 50`, `maxItems: 500`.
- **Daily alerts**: schedule daily, `lookbackDays: 7`, keep the caps generous, set `webhookUrl`.
- **Fresh start without a backlog**: run once with `firstRunMode: "baseline"`, then schedule normally. Only notices posted after the baseline run will be emitted.
- **Several watch lists**: use a different `monitorId` per filter set (for example one per recruiter territory).
- **Large layoffs only**: `minEmployees: 500`.

### Pricing

$0.005 per run plus $0.02 per emitted notice. Runs that find nothing new cost only the start fee.

# Actor input Schema

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

Two-letter state codes to monitor. Supported: CA, TX, NY, FL, PA, WA, IN, MD, VA, CO, UT, AL, DC, OR, WI.

## `companyKeywords` (type: `array`):

Only emit notices whose company name contains one of these words (case-insensitive). Leave empty for all companies.

## `minEmployees` (type: `integer`):

Skip notices affecting fewer workers than this. 0 disables the filter.

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

Only consider notices posted (or dated) within this many days. 0 disables the window.

## `maxNewPerState` (type: `integer`):

Maximum new or updated notices emitted per state per run. Notices that do not fit carry over to the next run.

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

Maximum notices emitted per run across all states.

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

emitAll: the first run emits every notice 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 with up to 50 notices to this URL.

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

Name of this monitor. Each ID keeps its own memory of seen notices, so you can run several monitors with different filters.

## `proxyConfiguration` (type: `object`):

Optional. All state feeds are reachable without a proxy; set one only if your runs get blocked.

## Actor input object example

```json
{
  "states": [
    "CA",
    "TX",
    "NY"
  ],
  "companyKeywords": [],
  "minEmployees": 0,
  "lookbackDays": 14,
  "maxNewPerState": 5,
  "maxItems": 10,
  "firstRunMode": "emitAll",
  "webhookUrl": "",
  "monitorId": "default"
}
```

# Actor output Schema

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

Dataset of new or updated WARN layoff notices (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 = {
    "states": [
        "CA",
        "TX",
        "NY"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("outstanding_vegetable/layoff-notice-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 = { "states": [
        "CA",
        "TX",
        "NY",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("outstanding_vegetable/layoff-notice-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 '{
  "states": [
    "CA",
    "TX",
    "NY"
  ]
}' |
apify call outstanding_vegetable/layoff-notice-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,outstanding_vegetable/layoff-notice-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/8fKnZUgFq8n7DODcY/builds/PJfqZhIWVB6MB1pvS/openapi.json
