# Layoffs Tracker — WARN Notices & Company Layoffs (`inovaflow/layoffs-tracker`) Actor

Official US WARN notices from 13 state portals plus announced company layoffs, as one typed, deduplicated feed: company, employees affected, notice and effective dates, location, layoff or closure, notice link. Incremental, dataset-only, MCP-ready.

- **URL**: https://apify.com/inovaflow/layoffs-tracker.md
- **Developed by:** [inovaflow](https://apify.com/inovaflow) (community)
- **Categories:** Lead generation, Business, News
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 layoff events

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

**A layoffs tracker for agents and GTM teams: every official WARN notice from 13 US states, plus announced company layoffs, as one clean, deduplicated, typed feed.**

If you sell to companies, a layoff is one of the loudest signals there is. Budgets get cut, tools get consolidated, a VP who owned your deal leaves, and a new one arrives with a mandate to change things. In the US, employers planning a plant closing or mass layoff must file a **WARN notice** with the state weeks before it happens. That makes these the earliest hard facts about a downsizing. The catch is that every state publishes them differently: spreadsheets, CSV exports, HTML tables, month-by-month lists, amendments filed as separate rows. Nobody has time to check a dozen state sites every week.

This Actor does it for you. It reads the official WARN filings of 13 states and the layoffs companies announce publicly. It turns them into **one row per workforce-reduction event** — company, employees affected, notice and effective dates, location, layoff vs closure, and the link to the official notice — newest first. Schedule it, and each run returns **only what is new or amended since the last run**.

Raw facts only. No AI summaries or scores: your agent decides what a layoff means for your pipeline.

### Who it's for

- **AI agents and MCP clients** that poll for downsizing events on a schedule and act on them. Every field is typed, dates are ISO, and a missing fact is `null`, never a guess.
- **Sales and RevOps teams** watching their accounts for budget cuts, tool consolidation and churn risk. Add your account list and get only their events.
- **Recruiters and staffing firms** who want to reach displaced talent the week a notice is filed, by state, city and headcount.
- **Investors and analysts** tracking distress and restructuring in a sector or region.

### What you get

One row per event:

| Field | Example |
|---|---|
| `company` | `Microsoft` |
| `eventType` | `layoff` · `closure` · `furlough` · `relocation` · `unknown` |
| `employeesAffected` | `277` (a number only when the filing states one) |
| `noticeDate` / `effectiveDate` | `2026-09-22` / `2026-11-21` |
| `state` / `city` / `county` / `location` | `WA` / `Redmond` / — / `Redmond, WA` |
| `industry` | `54 Professional Scientific and Technical Services` (where the state publishes it) |
| `sourceType` | `warn-notice` or `news` |
| `noticeUrl` | the official notice or listing |
| `isAmended`, `sources[]` | every filing and amendment folded into this event |
| `isNewSinceLastRun`, `isUpdatedSinceLastRun`, `firstSeenAt` | the incremental feed |
| `canonicalId` | a stable ID for the event across runs |

It also keeps the source's own wording next to the parsed value: `eventTypeRaw` ("Mass Layoff - No Recall"), `employeesAffectedText` ("39 (remote workers in MD)") and `effectiveDateText` ("9/16/26 - 1/8/27"). You can always check a row against the filing.

```json
{
  "canonicalId": "wa-91c678630f449839",
  "company": "Microsoft",
  "eventType": "layoff",
  "eventTypeRaw": "Layoff Permanent",
  "isTemporary": false,
  "employeesAffected": 277,
  "noticeDate": null,
  "receivedDate": "2026-09-22",
  "effectiveDate": "2026-11-21",
  "filedMonth": "2026-09",
  "state": "WA",
  "city": "Redmond",
  "location": "Redmond, WA",
  "sourceType": "warn-notice",
  "sourcePortal": "WA ESD WARN database",
  "noticeUrl": "https://…",
  "isAmended": false,
  "sourceCount": 3,
  "isNewSinceLastRun": true,
  "isUpdatedSinceLastRun": false
}
```

### Coverage

**Official WARN notices:** California, Colorado, Florida, Illinois, Indiana, Maryland, New Jersey, New York, Ohio, Pennsylvania, Texas, Washington and Wisconsin.

**Announced layoffs:** company layoffs reported in the news that never reach a WARN filing, such as tech and remote teams, cuts below the WARN threshold and staff outside the US. Only headlines that name the company and state a cut are used. Speculation ("could cut", "weighs layoffs") and roundups are skipped. When an announcement matches a WARN notice (same company, same place or size, within 30 days), it is attached to the notice as a source instead of listed twice.

Some states publish less than others. New Jersey and Pennsylvania give only the month a notice was posted, so `noticeDate` is `null` and `filedMonth` is set. New Jersey, Texas and Florida publish no layoff/closure type, so `eventType` is `unknown`. We never fill a gap with a guess. More states are being added. If a state can't be read on a given run, the run still succeeds with the others and says which one was missing.

### How deduplication works

- **Re-filed and amended notices** become one event. The latest filing's facts win, and every filing is kept in `sources[]` with `isAmended: true`.
- **Two sites of the same employer** stay two events: each WARN notice covers one site.
- **Several rows filed the same day for the same site** (one per unit or department) become one event with the summed headcount.
- **Many articles about the same announcement** become one event.

### How to use it

1. **Run it as it is.** You get every notice filed in the last 30 days across all covered states, plus announced layoffs.
2. **Narrow it down:** pick states, add your **companies to watch** (names or domains), filter by event type, headcount or effective date, or add keywords such as `hospital` or `warehouse`.
3. **Make it a feed.** Turn on **Only new or amended since the last run**, give it a **Watch ID**, and schedule it daily or weekly. The first run is the baseline. After that, each run returns only new notices and notices an amendment changed.

For AI agents: the input is fully optional, and field names are forgiving (`states`, `companies`, `daysBack`, `maxResults`, …). The run summary in the key-value store (`OUTPUT`) lists events per state, which states were read, and anything that was skipped.

### Pricing

Pay per event: **$0.01 per layoff event delivered to your dataset**, plus the standard Actor start fee. Duplicates, amendments of an event you already have, filtered-out rows and empty runs are **never charged**. A typical daily run over all states delivers 5–15 new events a day (about 5–15¢), and a 30-day backfill of every covered state is roughly 250 events.

### Good to know

- WARN law covers larger layoffs (typically 50+ workers at a site, with state variations). Smaller cuts show up only if a company announces them.
- `employeesAffected` is what the employer filed. The final number can differ, and amendments are picked up automatically.
- `companyDomain` is left empty on purpose: we don't guess domains from names. Pair this Actor with a company-enrichment Actor to add domains and decision-makers to the rows you care about.
- Dates are ISO (`YYYY-MM-DD`). A date range keeps its first date in the parsed field and the full wording in the `…Text` field.

# Actor input Schema

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

Two-letter codes or names of the states whose WARN notices to read, one per line. Covered: CA, CO, FL, IL, IN, MD, NJ, NY, OH, PA, TX, WA, WI. Leave empty for all of them. A state that is not covered yet is skipped with a note in the run summary.

## `companies` (type: `array`):

Only return events for these companies — names or domains, one per line (`Oracle`, `amazon.com`). Matches legal names, 'doing business as' names and subsidiaries named after the company. Leave empty for every employer.

## `filedWithinDays` (type: `integer`):

How far back to look, by the date the notice was filed or posted (the article date for announced layoffs). 30 days covers one monthly review; widen it for a backfill.

## `includeTechLayoffs` (type: `boolean`):

Add layoffs announced in the press that have no WARN filing (tech and remote teams, non-US staff, cuts below the WARN threshold). Only headlines that name the company and state a cut are used; an announcement of a cut that also has a WARN notice is attached to that notice instead of listed twice.

## `eventTypes` (type: `array`):

Only these event types. Empty = all. `unknown` is a notice whose state portal does not publish the type (New Jersey, Texas, Florida).

## `keywords` (type: `array`):

Keep only events whose company, industry, city, county or headline contains one of these words (`warehouse`, `hospital`, `software`, `Seattle`). Case-insensitive, combined with OR.

## `minEmployeesAffected` (type: `integer`):

Drop smaller events. Events whose headcount the source does not state are dropped when this is set.

## `maxEmployeesAffected` (type: `integer`):

Drop larger events. Events with an unstated headcount are kept.

## `effectiveAfter` (type: `string`):

Keep events whose layoff / closure date is on or after this day (YYYY-MM-DD). Events without an effective date are dropped when set.

## `effectiveBefore` (type: `string`):

Keep events whose layoff / closure date is on or before this day (YYYY-MM-DD) — e.g. cuts landing this quarter. Events without an effective date are dropped when set.

## `onlyNewSinceLastRun` (type: `boolean`):

Return only events this watch has not delivered before, plus events an amended notice changed. The first run of a watch returns everything and becomes the baseline.

## `watchId` (type: `string`):

Name for this feed's memory, e.g. `sales-west` — runs with the same watch ID share it. Leave empty and a watch ID is derived from the states, companies and filters, so repeated runs with the same scope share memory automatically.

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

Cap on delivered rows (newest first). You are only charged for delivered rows.

## `includeWarnNotices` (type: `boolean`):

Switch off to get only announced layoffs from the news.

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

How many state portals to read at once. These are public government sites — the default is polite.

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

Not needed for most runs — the state portals are public and a blocked request is retried through Apify Proxy automatically. Set a US residential proxy if a state keeps coming back unreadable.

## Actor input object example

```json
{
  "states": [
    "CA",
    "NY",
    "TX"
  ],
  "companies": [
    "Oracle",
    "Microsoft",
    "Kaiser"
  ],
  "filedWithinDays": 30,
  "includeTechLayoffs": true,
  "eventTypes": [],
  "onlyNewSinceLastRun": false,
  "maxResults": 500,
  "includeWarnNotices": true,
  "maxConcurrency": 4,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `layoffs` (type: `string`):

One row per deduplicated WARN notice or announced layoff: company, type, employees affected, notice and effective dates, location, industry and the official notice link.

## `changes` (type: `string`):

The incremental feed of a scheduled watch: new events and events an amended notice changed.

## `allFields` (type: `string`):

Every field of every row, including the source wording, all filings in sources\[] and the canonical event ID.

## `summary` (type: `string`):

Events per state and type, what is new since the previous run, which states were read or unreadable, and the watch ID.

# 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"
    ],
    "filedWithinDays": 30
};

// Run the Actor and wait for it to finish
const run = await client.actor("inovaflow/layoffs-tracker").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"],
    "filedWithinDays": 30,
}

# Run the Actor and wait for it to finish
run = client.actor("inovaflow/layoffs-tracker").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"
  ],
  "filedWithinDays": 30
}' |
apify call inovaflow/layoffs-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,inovaflow/layoffs-tracker"
        }
    }
}
```

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/7SQZhP00veTJfdN9Z/builds/Jesbjz474xtcP5FsS/openapi.json
