Government and public records
EPA ECHO Enforcement Cases by Company or Facility
EPA's Enforcement and Compliance History Online (ECHO) records federal and state environmental enforcement cases. Its case search cannot filter by company name, so this Actor finds a company's facilities first and then reads their cases, returning one normalized record per case.
OSHA Inspections by Company + EPA Enforcement Report. Pay per result; see the Store page for current pricing.
The source and its limits
EPA ECHO web services (echodata.epa.gov) publish enforcement cases with the case number and name, category, status, primary law (CAA, CWA, RCRA and others), filing and settlement dates, federal and state or local penalties, and the outcome. No key is needed.
Because ECHO's company-name filter for cases is not usable, the Actor runs a facility search for each company and then reads the case lists of the matched facilities. By default it checks only facilities that ECHO flags with formal enforcement actions or penalties; epaAllFacilities checks every matched facility, up to maxFacilitiesPerCompany. You can also look up a facility directly by its 12-digit FRS registry id with frs:<id>.
ECHO includes state-led and locally led cases that states report to EPA. State agencies' own systems are not queried.
Limits worth knowing
- A very broad name can exceed ECHO's query limit and returns a
QUERY_TOO_BROADitem; add a state or ZIP. - A case found through a matched facility can be filed under another party's name. Its
matchConfidenceis thenlowandmatchNotesays so. - Agency data lag EPA's internal systems, and statuses and penalties can change after a case is first published.
Fields you get
| Field | What it holds |
|---|---|
recordId, entityName | epa:<case number> and the case or defendant name |
facilityName, facilityRegistryId | The matched facility and its FRS registry id |
address, city, state, zip, naicsCode | Facility location and industry |
actionDate, closedDate | Case filed (or settled, when no filing date) |
actionType, status, outcome | For example Administrative - Formal, Final Order Issued, Final Order With Penalty |
lawOrProgram | Primary law: CAA, CWA, RCRA and others |
penaltyUsd | Penalty as published |
matchBasis, matchConfidence, matchedName, sourceUrl | How the record matched, and the ECHO case report link |
Example output
One EPA case from a quick-start run on 2026-11-01 (from the Actor's README, raw fields omitted):
{
"recordType": "enforcement-case",
"agency": "EPA",
"recordId": "epa:09-2019-0029",
"query": "Tesla",
"entityName": "Tesla Motors, Inc.",
"facilityName": "TESLA MOTORS INC",
"facilityRegistryId": "110000482898",
"address": "45500 FREMONT BLVD",
"city": "FREMONT",
"state": "CA",
"zip": "94538",
"naicsCode": "336111",
"actionDate": "2019-03-28",
"closedDate": null,
"actionType": "Administrative - Formal",
"status": "Final Order Issued",
"penaltyUsd": 31000,
"lawOrProgram": "RCRA",
"outcome": "Final Order With Penalty",
"sourceUrl": "https://echo.epa.gov/enforcement-case-report?id=09-2019-0029",
"matchBasis": "facility-name",
"matchConfidence": "medium",
"matchedName": "TESLA MOTORS INC",
"fetchedAt": "2026-11-01T13:01:08.211Z"
}
Use cases
Due diligence on a facility
Look up a plant by FRS id and read every case filed against it, with the law involved and the outcome.
Supplier screening without a key
Run sources: ["epa"] for a list of supplier names and states. EPA needs no key, so this works before you request a DOL key.
Scheduled watch
Turn on monitor to write only new or changed cases, fingerprinted by status, penalty and closing date, and post them to a webhook.
How to run it
You need an Apify account and its API token (Console → Settings → API & Integrations). Each run is billed per result to your account; set a maximum total charge for the run to cap spend.
With curl
The sync endpoint starts a run, waits up to 300 seconds and returns the dataset items. EPA ECHO cases for facilities matching "Tesla" in California (the Actor's own quick-start input):
export APIFY_TOKEN=<your Apify API token>
curl -sS -X POST \
"https://api.apify.com/v2/acts/owlabee~osha-epa-inspections/run-sync-get-dataset-items" \
-H "Authorization: Bearer ${APIFY_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"companies": [
"Tesla"
],
"state": "CA",
"sources": [
"epa"
]
}'
From an AI agent (MCP)
Apify hosts an MCP server. This config exposes only this Actor to the agent:
{
"mcpServers": {
"owlabee-osha-epa-inspections": {
"url": "https://mcp.apify.com/?tools=owlabee/osha-epa-inspections",
"headers": {
"Authorization": "Bearer ${APIFY_TOKEN}"
}
}
}
}
Then ask, for example: Which EPA enforcement cases are on record for Tesla facilities in California, and under which law was each filed?
Python, Node.js and more examples
Runnable curl.sh, python.py, node.mjs and mcp.json files for this Actor are in examples/osha-epa-inspections on GitHub.
FAQ
Does EPA ECHO need an API key?
No. ECHO web services are public. Only the OSHA source needs a key.
Why does it search facilities first?
ECHO's company-name filter for cases is not usable, so cases are found through the facilities that match the name.
Are state cases included?
Cases that states report to EPA are in ECHO and are returned (the lead agency is in the raw case data). State agencies' own databases are not queried.
How do I avoid records for similarly named companies?
Add a state or ZIP, raise minMatchConfidence, or look up facilities by FRS id.
What does the summary record contain?
Per company: record counts and penalty totals by agency, open records, counts by match confidence, the most recent action and every matched name.