A free WARN Act API: 61,431 layoff notices, 48 states, one GET request

Every US state has to publish the mass-layoff notices employers file under the WARN Act, and every state publishes them differently — a portal here, a spreadsheet there, a PDF somewhere else. We scrape all 48 of them every morning, normalize them into one 11-field schema, resolve the employer names, and serve the result as plain JSON and CSV over HTTPS. No key, no signup, no rate limit, CORS open. If you have ever wanted to put real layoff data in something you are building, this is one curl away.

61,431notices, one GET away48 state agencies, back to 1988
0keys, tokens or signupsplain HTTPS, CORS open to any origin
59endpoints published41.7 MB of JSON/CSV, rebuilt daily
CC BY 4.0licenceuse it commercially, credit WARN Feed

Try it now — GET /data/latest.json Read the 11-field schema

Quickstart

# the whole archive as JSON (no key, no header)
curl -sL https://approjects-warn-act-notices.static.hf.space/data/warn_notices.json | jq '.[0]'

# just what is new — poll this one daily
curl -sL https://approjects-warn-act-notices.static.hf.space/data/latest.json | jq '.count, .generated_at'

# poll politely: send back the ETag you got last time — unchanged = 304, zero bytes
curl -sL -o latest.json -D - -H 'If-None-Match: "<etag from last run>"' https://approjects-warn-act-notices.static.hf.space/data/latest.json

# streaming: one notice per line, no 20 MB buffer
curl -sL https://approjects-warn-act-notices.static.hf.space/data/warn_notices.ndjson | head -n 3 | jq -c '{company_canonical, state, notice_date}'

# one state, as CSV
curl -sL https://approjects-warn-act-notices.static.hf.space/data/by-state/ca.csv | head -3
# python: the recent window straight into pandas
import pandas as pd
df = pd.read_csv("https://approjects-warn-act-notices.static.hf.space/data/latest.csv")
print(len(df), df.columns.tolist())
# duckdb: query the archive in place, no download step
SELECT state, count(*) AS notices
FROM read_csv_auto('https://approjects-warn-act-notices.static.hf.space/data/latest.csv')
GROUP BY 1 ORDER BY 2 DESC;
// browser javascript: CORS is open, so this works from any page
const r = await fetch("https://approjects-warn-act-notices.static.hf.space/data/coverage.json");
const cov = await r.json();
console.log(Object.keys(cov.states).length, "state agencies", cov.generated_at);

Endpoints

Base URL https://apventureengine.github.io/warn-act-notices. Sizes and row counts below are from the build that wrote this page, so they are what you will actually download today.

EndpointWhatRecordsSizeUse it for
/data/warn_notices.jsonWhole archive, JSON61,431 notices20.1 MBEvery notice we hold, one object per notice, 11 normalized fields. The file a backend or notebook wants.
/data/warn_notices.ndjsonWhole archive, NDJSON61,431 notices18.7 MBThe same notices, one JSON object per line, so a stream reader never buffers 20 MB: <code>curl … | head</code>, <code>jq -c</code>, log shippers, DuckDB.
/data/latest.csvRecent notices, CSV88 notices16 kBThe rolling window of notices added lately — the cheap poll for a daily job.
/data/latest.jsonRecent notices, JSON88 notices36 kBSame window as latest.csv with a header block: generated_at, window_days, count.
/datapackage.jsonSchema (Frictionless Table Schema)11 fields3 kBField names, types and descriptions for the CSVs. Read this before you map columns.
/data/coverage.jsonPer-state coverage + freshness48 states11 kBFor every state agency: rows held, last scrape time, latest notice date, status. This is the file to poll if you want to know whether a state is behind.
/data/stale.jsonSources behind their usual cadence10 late sources2 kBThe subset of coverage.json that is late, with how late. Machine-readable honesty.
/data/trends.jsonMonthly series24 months56 kBNotices and workers per month, national and by state — for charts without re-aggregating the archive.
/data/search-index.jsonEmployer search index29,971 employers2.7 MBCanonical employer names with their notice counts; powers client-side search.
/data/badge.jsonShields.io endpoint badge—153 BDrop a live 'notices on record' badge in your own README.
/feed.xmlRSS of new notices50 items29 kBFor feed readers and RSS-to-anything plumbing. No key.

Per-state CSVs (48 states)

AK · AL · AZ · CA · CO · CT · DC · DE · FL · GA · HI · IA · ID · IL · IN · KS · KY · LA · MA · MD · ME · MI · MN · MO · MS · MT · NC · ND · NE · NJ · NM · NV · NY · OH · OK · OR · PA · RI · SC · SD · TN · TX · UT · VA · VT · WA · WI · WV

Pattern: https://approjects-warn-act-notices.static.hf.space/data/by-state/<two-letter-code-lowercase>.csv

California16,640 noticesNew York6,515 noticesIllinois4,847 noticesFlorida3,116 noticesTexas2,391 noticesNew Jersey2,330 noticesMichigan2,202 noticesPennsylvania1,896 noticesWashington1,519 noticesOregon1,371 noticesMaryland1,277 noticesIndiana1,182 notices
Notices on record per state agency, top 12 of 48 (61,431 in total). Each state also has its own CSV endpoint below. · Source: data/coverage.json, written by the same build as this page · as of 2026-09-25

Schema

The same fields in every endpoint, machine-readable at /datapackage.json:

FieldTypeMeaning
idstringStable dedupe id (source record number where available, else content hash)
statestring2-letter US postal code
companystringEmployer name exactly as published by the state
company_canonicalstringCleaned employer name; groups the same employer across states, renotices and store numbers
company_dbastringTrade name when the state published a dba/aka alias
locationstringCity/county/address, best effort
employees_affectedintegerWorkers affected; empty if the state omitted it
notice_datedateISO date the notice was received/posted (NJ: month precision, day pinned to 01)
effective_datedateISO date the layoff/closure takes effect
notice_typestringLayoff/closure label as published
first_seendatetimeUTC timestamp the pipeline first saw this notice

What this API does not do

Three things, and they are the honest reason a paid tier exists rather than a fake one:

If what you actually wanted was an alert, not a file: WARN Watch runs your employer list — up to 500 names, or whole states — against every daily refresh for a year and pushes the hits to a private page, an RSS feed, a calendar and your Slack, Discord or Teams webhook. WARN Watch — $49/year, one payment → Or take the free 30-day trial (3 employers or 1 state, no card).

Ask it in plain English: the MCP server

If you work inside an AI assistant rather than a notebook, you do not have to download anything. warn-mcp exposes this dataset as six read-only Model Context Protocol tools — employer search, the rolling new-notice feed, one employer’s whole history back to 1988, monthly state totals, the daily revision log, and a freshness check — so you can just ask:

“Has Starbucks filed any WARN notices this year, and in which states?” · “What layoffs were announced in Texas in the last two weeks?” · “Which state had the most workers affected in 2026?”

Claude Desktop, Cursor, Continue, Claude Code — no key, no account: install the one-file bundle from the latest release, or one line in a terminal:

claude mcp add warn -- uvx --from git+https://github.com/APVentureEngine/warn-mcp warn-mcp

ChatGPT and Claude.ai connectors, n8n, anything that wants a URL: the same six tools are hosted over Streamable HTTP at https://mQpACuIiQcnwK93JY.apify.actor/mcp. One honest caveat — the host meters compute to whoever calls it, so that route needs your own free Apify API token in an Authorization: Bearer header. We charge nothing for it and store nothing about your queries; the bundle above needs no token at all, so prefer it when your client supports it.

Both routes are listed as io.github.APProj/warn-mcp in the official MCP registry. Source and issues: the warn-mcp repository.

FAQ

Is it really free, and what is the catch?

Really free, CC BY 4.0, no account. The catch is honest and structural: these are static files rebuilt once a morning, so there are no query parameters, no per-request filtering and no push. If that is enough, you never have to pay us.

How fresh is it?

The build that produced this page finished at 2026-09-25T00:11:25.651533Z and the newest notice in it is dated 2026-09-25. Every state is re-scraped in the same run; /data/coverage.json carries the per-state scrape time and /data/stale.json lists any source that is behind its usual cadence, so you never have to guess whether silence means 'no layoffs' or 'our scraper broke'.

Why does curl print 'Found. Redirecting to…' for the archive?

Files over about 10 MB (warn_notices.json, warn_notices.ndjson) answer 302 to a CDN copy; everything smaller is served directly. Follow redirects — curl -L, and every HTTP library does it by default. The CDN answers with the same access-control-allow-origin: * and its own ETag (measured 2026-09-13).

Can I call it from a browser?

Yes. The host answers access-control-allow-origin: * on these files, so fetch() works from any origin with no proxy of your own.

Rate limits?

None imposed by us, and we would rather you were polite than throttled. Every file is served with an ETag, and a request carrying If-None-Match with the last value you saw gets 304 Not Modified and no body (measured 2026-09-13: 304, 0 bytes). Last-Modified is not sent, so If-Modified-Since does nothing — use the ETag. Poll /data/latest.json (small) and only re-pull the archive when its generated_at moves. Mirroring the files is explicitly allowed — that is what CC BY 4.0 means.

What do I have to put in my app?

Credit "WARN Feed" with a link to this site. That is the whole obligation.

Will the fields change under me?

The schema lives in /datapackage.json and is the same contract the CSVs and the Hugging Face mirrors use. New columns get appended; if one ever has to change meaning it is written up in the repository's release notes, not silently.

I need something you do not publish.

Ask for it — open an issue and say which field, state or shape you need. Requests that are a small transform of what we already scrape usually ship the same week, because the pipeline runs every day anyway.

Mirrors and other shapes

The same data, maintained by the same daily build, if a different surface suits you better: the Git repository (versioned, every change is a commit) · the Hugging Face dataset (parquet-backed viewer, datasets.load_dataset) · RSS · the web search UI.