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.
Try it now — GET /data/latest.json Read the 11-field schema
# 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);
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.
| Endpoint | What | Records | Size | Use it for |
|---|---|---|---|---|
/data/warn_notices.json | Whole archive, JSON | 59,806 notices | 19.5 MB | Every notice we hold, one object per notice, 11 normalized fields. The file a backend or notebook wants. |
/data/warn_notices.ndjson | Whole archive, NDJSON | 59,806 notices | 18.2 MB | The 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.csv | Recent notices, CSV | 8,605 notices | 1.3 MB | The rolling window of notices added lately — the cheap poll for a daily job. |
/data/latest.json | Recent notices, JSON | 8,605 notices | 3.2 MB | Same window as latest.csv with a header block: generated_at, window_days, count. |
/datapackage.json | Schema (Frictionless Table Schema) | 11 fields | 3 kB | Field names, types and descriptions for the CSVs. Read this before you map columns. |
/data/coverage.json | Per-state coverage + freshness | 48 states | 11 kB | For 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.json | Sources behind their usual cadence | 9 late sources | 2 kB | The subset of coverage.json that is late, with how late. Machine-readable honesty. |
/data/trends.json | Monthly series | 24 months | 55 kB | Notices and workers per month, national and by state — for charts without re-aggregating the archive. |
/data/search-index.json | Employer search index | 29,131 employers | 2.6 MB | Canonical employer names with their notice counts; powers client-side search. |
/data/badge.json | Shields.io endpoint badge | — | 153 B | Drop a live 'notices on record' badge in your own README. |
/feed.xml | RSS of new notices | 50 items | 29 kB | For feed readers and RSS-to-anything plumbing. No key. |
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
The same fields in every endpoint, machine-readable at /datapackage.json:
| Field | Type | Meaning |
|---|---|---|
id | string | Stable dedupe id (source record number where available, else content hash) |
state | string | 2-letter US postal code |
company | string | Employer name exactly as published by the state |
company_canonical | string | Cleaned employer name; groups the same employer across states, renotices and store numbers |
company_dba | string | Trade name when the state published a dba/aka alias |
location | string | City/county/address, best effort |
employees_affected | integer | Workers affected; empty if the state omitted it |
notice_date | date | ISO date the notice was received/posted (NJ: month precision, day pinned to 01) |
effective_date | date | ISO date the layoff/closure takes effect |
notice_type | string | Layoff/closure label as published |
first_seen | datetime | UTC timestamp the pipeline first saw this notice |
Three things, and they are the honest reason a paid tier exists rather than a fake one:
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.
The build that produced this page finished at 2026-09-13T01:40:52.945448Z and the newest notice in it is dated 2026-09-11. 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'.
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).
Yes. The host answers access-control-allow-origin: * on these files, so fetch() works from any origin with no proxy of your own.
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.
Credit "WARN Feed" with a link to this site. That is the whole obligation.
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.
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.
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.