# US Industrial Facility Data — EPA ECHO Compliance & Enforcement (`foxlabs/epa-facility-compliance-data`) Actor

Look up US industrial facilities in EPA ECHO by company name, city or state. Returns facility name and address, SIC and NAICS codes, Clean Air/Water and RCRA compliance status, inspection counts, formal actions and penalties.

- **URL**: https://apify.com/foxlabs/epa-facility-compliance-data.md
- **Developed by:** [Berkan Kaplan](https://apify.com/foxlabs) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$4.00 / 1,000 company records

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

## US Industrial Facility Data — EPA ECHO Compliance & Enforcement

EPA ECHO is the compliance record of every regulated industrial site in the United States — factories, refineries, treatment plants, waste handlers. Query it by company name and you get their facilities, what industry they are in, whether the EPA has found violations, inspected them or fined them, their greenhouse-gas and toxic-release figures — for the facilities that report to the Toxics Release Inventory, the parent company and the public contact they filed — and, from OSHA's injury-reporting data, how many people work at the site and the employer's EIN.

**No API key · Official source · Pay only for delivered rows · Same schema across the series**

### What data do you get?

| Field | Description |
|---|---|
| `companyName` | Facility name as registered with the EPA |
| `registrationNumber` | EPA Registry ID (FRS identifier) |
| `status` | `active` when ECHO flags the facility active in at least one EPA program, `inactive` otherwise |
| `address` | Facility street address |
| `city`, `county`, `state`, `postalCode` | Facility location |
| `industry` | NAICS title of `industryCode`, from ECHO's own NAICS lookup |
| `industryCode` | First NAICS code on the record, falling back to SIC |
| `naicsCodes`, `sicCodes` | All NAICS and SIC codes on the record |
| `employees` | Annual average employees at the facility's address, from the operator's OSHA Form 300A (several establishments of the same employer at one site are summed) |
| `employeesAsOf`, `employeesSource` | Calendar year of that figure, and how it was put together |
| `employerName`, `taxNumber` | The employer that files OSHA Form 300A for the site, and its EIN — often a subsidiary (TYSON FARMS, INC. at a Tyson Foods plant) |
| `taxNumberSource` | Where `taxNumber` comes from, and whether it equals the parent company's EIN on SEC EDGAR |
| `parentCompany` | Parent company declared on the facility's TRI filing (EPA-standardized name) |
| `parentDuns` | D-U-N-S number of that parent, as filed |
| `parentEin`, `parentTicker`, `parentCik` | EIN, stock ticker and EDGAR CIK of that parent, when it is listed on a US exchange |
| `foreignParentCompany` | Foreign parent company, when one is declared |
| `contactName`, `phone`, `email` | The public contact the facility named on its TRI filing |
| `triFacilityId` | TRI facility ID the parent and contact fields come from |
| `complianceStatus` | Overall ECHO compliance status |
| `airComplianceStatus` | Clean Air Act compliance status |
| `waterComplianceStatus` | Clean Water Act compliance status |
| `wasteComplianceStatus` | RCRA hazardous waste compliance status |
| `significantNoncompliance` | True when flagged a High Priority / Significant / Serious violator |
| `quartersInNoncompliance` | Quarters in non-compliance out of the last twelve |
| `inspectionCount` | Compliance monitoring activities in the last five years |
| `lastInspectionOn` | Date of the most recent inspection |
| `formalActionCount` | Formal enforcement actions in the last five years, all programs |
| `penaltyCount` | Penalties assessed in the last five years |
| `penaltyTotal` | Total penalties assessed in the last five years, all programs, USD |
| `lastPenaltyOn`, `lastPenaltyAmount` | The most recent penalty on record and its amount, USD |
| `ghgEmissionsTonsCo2e` | Greenhouse-gas emissions reported to the EPA GHG program, metric tons CO2e per year |
| `triReleasesLbs` | Toxic chemical releases reported to TRI for the latest reporting year, pounds |
| `latitude`, `longitude` | Facility coordinates |
| `sourceUrl` | ECHO detailed facility report |

Every row also carries `query` (what you asked for), `scrapedAt` (ISO timestamp) and, when a
lookup fails, `error` explaining why.

**What the EPA does not publish.** A row here is a facility, not a company record, so
`legalForm`, `incorporatedOn`, `dissolvedOn`, `capital`, `website` and `officers` — part of the
company schema shared across this series — stay `null`. `employees` and `taxNumber` come from OSHA
(see Notes and limits).

### Example output

A real row for Tesla's Fremont plant (contact details masked here; the dataset carries them in full):

```json
{
  "country": "US",
  "registry": "EPA ECHO",
  "companyName": "TESLA, INC.",
  "registrationNumber": "110000482898",
  "status": "active",
  "address": "45500 FREMONT BOULEVARD, FREMONT, CA, 94538",
  "city": "FREMONT",
  "county": "ALAMEDA",
  "state": "CA",
  "postalCode": "94538",
  "industry": "Automobile and Light Duty Motor Vehicle Manufacturing",
  "industryCode": "336110",
  "naicsCodes": ["336110", "336111", "33611", "336399"],
  "sicCodes": ["3711"],
  "taxNumber": "91-2197729",
  "employees": 16609,
  "employerName": "Tesla",
  "taxNumberSource": "OSHA Form 300A; the same EIN as the parent company on SEC EDGAR",
  "employeesAsOf": "2025",
  "employeesSource": "OSHA Form 300A annual average, calendar year 2025",
  "parentCompany": "TESLA INC",
  "parentEin": "91-2197729",
  "parentTicker": "TSLA",
  "contactName": "V••••• O•••••",
  "phone": "925-•••-••••",
  "email": "v•••••@tesla.com",
  "triFacilityId": "94538NWNTD45500",
  "complianceStatus": "Significant Violation",
  "airComplianceStatus": "Violation Unaddressed; Local Has Lead Enforcement",
  "waterComplianceStatus": "No Violation Identified",
  "wasteComplianceStatus": "No Violation Identified",
  "significantNoncompliance": true,
  "quartersInNoncompliance": 12,
  "inspectionCount": 8,
  "lastInspectionOn": "2025-07-23",
  "formalActionCount": 1,
  "penaltyCount": 1,
  "penaltyTotal": 275000,
  "lastPenaltyOn": "2022-02-17",
  "lastPenaltyAmount": 275000,
  "ghgEmissionsTonsCo2e": 47053,
  "triReleasesLbs": 2125966,
  "latitude": 37.49634,
  "longitude": -121.94826,
  "sourceUrl": "https://echo.epa.gov/detailed-facility-report?fid=110000482898"
}
```

### Input

```json
{
  "queries": ["Tesla","Dow Chemical","Nucor Steel"],
  "maxResultsPerQuery": 10,
  "maxConcurrency": 3,
  "includeRaw": false
}
```

| Input | What it does |
|---|---|
| `queries` | Company or facility names (`Tesla`, `Dow Chemical`). ECHO matches on the facility name as registered, which is usually the operating entity rather than the corporate parent. |
| `maxResultsPerQuery` | Caps how many rows one query may produce. |
| `maxConcurrency` | How many queries run at once. Lower it if the source starts throttling. |
| `includeRaw` | Attaches the source's untouched record under `raw`, for fields this actor does not map. |
| `requestDelayMs` | Politeness delay between requests. |
| `proxyConfiguration` | Optional. ECHO is an open federal API and rarely needs a proxy. |
| `state` | Two-letter state code (`TX`, `CA`) to search one state only. |

### What people use it for

- **Industrial site prospecting** — find every plant a manufacturer runs, with its industry, coordinates and the environmental contact it filed, for territory planning or selling compliance services.
- **ESG and supply-chain screening** — check a supplier's violation history, penalties, greenhouse-gas emissions and toxic releases before contracting.
- **Ownership mapping** — the TRI parent company shows who owns a plant today, even when the site still carries a former owner's name (a "Dow Chemical" plant now owned by Occidental, for example).
- **Environmental research** — assemble a facility panel by industry code and track compliance over time.

### Notes and limits

- ECHO is a two-step API — a search returns a query ID, then results are paged from it. The actor does both, so a query costs two requests, plus one run-wide request for the NAICS titles.
- **Parent company and contact come from the Toxics Release Inventory**, so only facilities that report to TRI carry them — large manufacturers mostly do, offices, service centres and small sites do not. Each such facility costs one more request (EPA Envirofacts); if it fails, the row keeps its ECHO fields and the log says which facility was affected.
- Facilities are registered under operating names, not corporate parents. Searching `Tesla` finds sites named Tesla; `parentCompany` then tells you who owns the TRI reporters among them.
- **Listed parents come from SEC EDGAR**, matched on the exact company name (the SEC's list of exchange-listed companies is read once per run, then one EDGAR record per parent). The EIN is the parent's, not the facility operator's — a plant's own operating company (e.g. NUCOR STEEL DECATUR LLC) has its own.
- **Headcount and EIN come from OSHA's Form 300A summaries** (Injury Tracking Application), which establishments with 20 or more employees in designated industries, and 250 or more in all others, file every year. A facility is matched at its address, then only when exactly one employer there passes every test: its name starts with the facility's or the parent's brand, its industry sector is the facility's, it is not a contractor on site (cleaning, security, catering, staffing…), and its EIN mostly files under that brand. In a 200-facility test that played no part in tuning the matching, 44.5% of facilities got a headcount (51% when searching company names, 26% for generic words such as *Plating* or *Foundry*) and a manual check of every match found no wrong employer. Small sites, offices and non-reporting industries stay empty.
- The EIN is the employer's own as it filed with OSHA (three quarters of the matches carry one); it equals the parent's SEC EIN when the parent runs the plant itself. An EIN one or two digits away from the parent's SEC EIN is a typo in the filing and is left out. The OSHA data is read through a monthly-rebuilt foXLabs index; if it cannot be reached, these fields stay empty and the run goes on.
- ECHO's name search matches loosely and its order is not relevance. The actor ranks up to 1,000 matches per query: names containing your search as whole words first, and among those the active facilities that report to TRI or the greenhouse-gas program — the plants — ahead of offices and service sites.
- Five-year figures (`inspectionCount`, `formalActionCount`, `penaltyCount`, `penaltyTotal`) follow ECHO's own window. `lastFormalActionOn` and `lastPenaltyOn` report the most recent event on record, which can be older.

### Where the data comes from

The EPA publishes ECHO as a documented open REST service with no key and no quota; the TRI facility records come from the EPA's Envirofacts data service, also keyless. Sources: [EPA Enforcement and Compliance History Online (ECHO)](https://echo.epa.gov/), [EPA Envirofacts](https://www.epa.gov/enviro/envirofacts-data-service-api), [SEC EDGAR](https://www.sec.gov/search-filings), [OSHA Injury Tracking Application data](https://www.osha.gov/Establishment-Specific-Injury-and-Illness-Data)

### FAQ

#### Is this EPA ECHO scraper free?

The data source is free and needs no API key — you pay only for the rows the run delivers ($0.004 each). Failed or empty lookups are never charged.

#### Do I need an API key or a login?

No. The EPA publishes ECHO and Envirofacts as open REST services with no key.

#### What can I search by?

By facility or parent company name (`Tesla`, `Dow Chemical`). Narrow with the `state` input, which maps onto ECHO's own state filter.

#### How current is the data?

Every run queries the source live, so results are as fresh as the source itself. ECHO refreshes from the underlying enforcement systems weekly; contact and parent details are read from the facility's TRI record in Envirofacts at run time.

#### How fast is it, and how many queries can I run?

Queries run concurrently (3 at a time by default, tunable in the input). A prefilled run finishes in well under a minute; the TRI lookups are what take the time. Large lists scale roughly linearly and stay well inside a normal run timeout.

#### Can I export the results to CSV, Excel or JSON?

Yes. Apify datasets export to CSV, Excel, JSON, XML and HTML, and can be pulled through the API or pushed to your own storage.

#### What happens when a query returns nothing?

You still get a row, carrying your original `query` and an `error` field explaining why. Nothing is silently dropped, and you are not charged for it.

#### Is scraping this data legal?

Yes. ECHO is published by the EPA precisely so the public can see the compliance history of regulated facilities, and the TRI public contact is the person a facility designates for public inquiries about its filing.

### Changelog

#### 0.1.10 — 2026-09-25 — site headcount and employer EIN (OSHA)

- **New: `employees`, `employeesAsOf`, `employeesSource`, `employerName`, `taxNumber`, `taxNumberSource`,** from OSHA's Form 300A summaries matched at the facility's address. 44.5% of facilities filled in a 200-facility test that played no part in tuning, no wrong employer found in a manual check of every match.
- **Fixed: `parentEin` showed `00-0000000` for Owens Corning plants.** EDGAR carries a placeholder of zeros for some companies; it is no longer passed on as an EIN.
- No pricing change.

#### 0.1.9 — 2026-09-25 — listed parent company: EIN and ticker

- **New `parentEin`, `parentTicker`, `parentCik`.** When the parent company a facility declares to the Toxics Release Inventory is listed on a US exchange, its EDGAR record gives the EIN and ticker: NUCOR CORP → NUE, 13-1860817; TESLA INC → TSLA. The match is on the exact company name — punctuation and the spelling of the legal suffix are evened out, the suffix itself must agree, and a name two listed companies share matches neither. Subsidiaries and private parents stay empty. 67% of rows in the prefilled run.
- SEC requests identify the actor and a contact address, as the SEC's fair-access policy asks.
- No pricing change.

#### 0.1.8 — 2026-09-25 — dates, coordinates and penalties corrected; industry, parent company and contacts added

- **Dates were wrong and are now correct.** ECHO writes dates as MM/DD/YYYY and they were read day-first: `10/16/2025` became the impossible `2025-16-10`, and `08/02/2002` became February 8 instead of August 2. This affected `lastInspectionOn`, `lastFormalActionOn` and `lastPenaltyOn` in every earlier version (7 of 33 rows carried an impossible date in a reference run of 0.1.7; the others with a day of 12 or less were silently swapped).
- **`longitude` is filled** (it was always empty: ECHO does not send it unless asked), and coordinates with exactly three decimals are no longer corrupted: the number parser read `41.389` as 41,389, which put 4 of 33 latitudes out of range in a reference run of 0.1.7.
- **`penaltyTotal` and `formalActionCount` now cover all programs**, as ECHO totals them over five years. They used to count Clean Air Act penalties only, and Clean Air Act (else drinking water) actions only — values rise for facilities fined under the water or waste programs.
- **New:** `industry` (NAICS title), `parentCompany`, `parentDuns`, `foreignParentCompany`, `contactName`, `phone`, `email` and `triFacilityId` (TRI reporters), `lastPenaltyAmount`, `ghgEmissionsTonsCo2e`, `triReleasesLbs`.
- **Better matches:** up to 1,000 search hits are ranked instead of the first 50, so `Tesla` now returns the Fremont, Nevada, Texas and Buffalo plants instead of service centres and streets named Tesla Road.
- README price corrected to the actual $0.004 per row (it said $0.005). No pricing change.

***

Built by [Fox Labs](https://apify.com/foxlabs) — B2B company intelligence from public sources, as clean JSON.

# Changelog

This Actor's version history is a separate document: https://apify.com/foxlabs/epa-facility-compliance-data/changelog.md

# Actor input Schema

## `queries` (type: `array`):

Company or facility names (`Tesla`, `Dow Chemical`). ECHO matches on the facility name as registered, which is usually the operating entity rather than the corporate parent.

## `maxResultsPerQuery` (type: `integer`):

How many rows a single query may produce.

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

How many queries to run at the same time. Lower it if the source throttles you.

## `includeRaw` (type: `boolean`):

Attach the source's untouched response under `raw`. Useful when you need a field this actor does not map.

## `requestDelayMs` (type: `integer`):

Politeness delay against a public source. Raise it for large runs.

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

Optional. ECHO is an open federal API and rarely needs a proxy.

## `state` (type: `string`):

Two-letter state code (`TX`, `CA`). Leave empty to search nationally.

## Actor input object example

```json
{
  "queries": [
    "Tesla",
    "Dow Chemical",
    "Nucor Steel"
  ],
  "maxResultsPerQuery": 10,
  "maxConcurrency": 3,
  "includeRaw": false,
  "requestDelayMs": 300,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "state": ""
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

# 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 = {
    "queries": [
        "Tesla",
        "Dow Chemical",
        "Nucor Steel"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("foxlabs/epa-facility-compliance-data").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 = { "queries": [
        "Tesla",
        "Dow Chemical",
        "Nucor Steel",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("foxlabs/epa-facility-compliance-data").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 '{
  "queries": [
    "Tesla",
    "Dow Chemical",
    "Nucor Steel"
  ]
}' |
apify call foxlabs/epa-facility-compliance-data --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,foxlabs/epa-facility-compliance-data"
        }
    }
}
```

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/qgWj4CxYpwoaaSbpR/builds/MmGe3AwW2g3OeZvS5/openapi.json
