# EPA TSCA CDR Chemical Site Screener - Manufacture & Import (`malonestar/epa-tsca-cdr-chemical-site-screener`) Actor

Which TSCA chemicals are manufactured or imported at a site, in what production volume, by whom, and with what worker-exposure context. Screens EPA's 2024 Chemical Data Reporting bulk file by radius, CASRN or state, and keeps a CBI-withheld volume distinct from a real zero.

- **URL**: https://apify.com/malonestar/epa-tsca-cdr-chemical-site-screener.md
- **Developed by:** [Kyle Maloney](https://apify.com/malonestar) (community)
- **Categories:** Real estate, Developer tools, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.40 / 1,000 cdr site-chemical rows

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## EPA TSCA CDR Chemical Site Screener

**Which TSCA chemicals are manufactured or imported at a given site, in what production volume, by whom, and with what worker-exposure and physical-form context** — from EPA's 2024 Chemical Data Reporting (CDR) public bulk file.

CDR is a different record class from anything else on the Store. TRI tells you what a facility **released**. RCRA tells you what it **discarded**. CDR tells you what it **makes and brings into the country** — 50,116 site-chemical records covering 4,527 sites and 8,613 substances, with parent-company ownership, production volumes in pounds, worker-exposure bands and physical form.

### The one thing that makes this different from a CSV dump

**43.15% of 2023 production volumes in this file are the literal string `CBI`** — a Confidential Business Information claim, not a number. Another 13.3% are blank. A build that runs `Number(x) || 0` over that column fabricates **28,278 zero-production sites**, and does it silently, on a green run.

This actor treats four states as four different facts, and never collapses them:

| `production_volume_2023_status` | Meaning | `production_volume_2023_lb` |
|---|---|---|
| `reported` | The submitter published a volume | a number (which may legitimately be `0`) |
| `cbi_withheld` | The submitter claimed it confidential | `null` |
| `not_known` | "Not Known or Reasonably Ascertainable" | `null` |
| `not_reported` | Blank for this record | `null` |

A live drift gate asserts this before a single billable row is produced: one probe pins the national CBI share inside a band, and a second pins a specific record — Shell's Norco, Louisiana refinery, CASRN 7704-34-9 (Sulfur) — where the 2023 production volume is withheld while the 2023 **export** volume (95,272,804 lb) is published on the same row. If a future change ever turned a withheld volume into a zero or a blank, that probe goes red and the run fails without billing.

### Who it is for

- **Phase I ESA consultants and environmental due-diligence teams** — a CDR filer at an address *is* a chemical manufacturer or importer at that address. Cross-references by EPA FRS ID into the EPA Contaminated Site Screener and EPA TRI Facility Release Rollup.
- **Chemical sourcing and supply-chain analysts** — reverse-map a CASRN to every US site that makes or imports it, with domestic and foreign parent companies and D\&B numbers.
- **EHS, product stewardship and TSCA regulatory affairs** — worker-exposure bands, maximum-concentration bands, physical forms and percent byproduct per site-chemical record.
- **PFAS and chemical-exposure litigation support** — who was making or importing a substance, where, and in what volume band, over 2020–2023.
- **CRE lenders and insurance underwriters** — chemical manufacturing intensity around a collateral address.

### Modes

```json
{
  "mode": "radius",
  "assets": [
    { "lat": 40.8249, "lon": -111.9238, "radiusMiles": 3, "label": "Salt Lake City refinery corridor" },
    { "lat": 29.99435, "lon": -90.40726, "radiusMiles": 3, "label": "Norco LA petrochemical corridor" }
  ],
  "maxResults": 150
}
```

```json
{ "mode": "chemical", "chemicalIds": ["7704-34-9"], "maxResults": 200 }
```

```json
{ "mode": "state", "states": ["CO"], "chemicalFilter": [], "maxResults": 250 }
```

`mode` is **required**, so a bare `{}` call is rejected before the run starts and nothing is billed. `maxResults` defaults to 200 **in code**, never as an input-schema default, because Apify injects schema defaults server-side into every run.

### What an empty answer means, and what it does not

Every run emits one `query_summary` row per scope carrying the denominators, and a plain-language `coverage_note`. This matters because **a location screen structurally cannot see 19.3% of this file**:

| Coordinate state | Rows | Why |
|---|---|---|
| Usable coordinate | 40,466 (80.74%) | EPA published a real point |
| Whole site identity `CBI` | 7,074 (14.12%) | Name, address, state, coordinates and FRS ID all withheld |
| Published as exactly `0.0 / 0.0` | 2,571 (5.13%) | A null-island sentinel, never a real location |
| Blank or unparseable | 5 | — |

So an empty radius result means *no CDR site with published coordinates reported here* — **not** that no chemical is manufactured or imported at that location. The `chemical` mode still returns the confidential-identity sites (flagged `site_identity_confidential: true`), because the substance and its volumes are public even when the site is not.

### Related actors on this Store

Join on `epa_frs_id`:

- [EPA Contaminated Site Screener](https://apify.com/malonestar/epa-contaminated-site-screener) — Superfund, RCRA, ECHO enforcement and tanks at the same coordinates.
- [EPA TRI Facility Release Rollup](https://apify.com/malonestar/epa-tri-facility-release-rollup) — reported **releases** to air, water and land, which CDR does not cover.
- [EPA RCRA Hazardous Waste Generator Rollup](https://apify.com/malonestar/epa-rcra-hazwaste-generator-rollup) — hazardous waste **generated**, by federal waste code.
- [EPA Drinking Water Quality Screener](https://apify.com/malonestar/epa-drinking-water-quality-screener) — SDWA violations and PFAS occurrence for the serving water system.
- [Site Due Diligence Bundle](https://apify.com/malonestar/site-due-diligence-bundle) — twenty regulatory layers for one coordinate in one call.

### Use as an MCP tool

This actor is callable by AI agents through Apify's hosted MCP server. Billing is unchanged when it is called as a tool, and a run that cannot answer fails without billing.

```json
{
  "mcpServers": {
    "apify": {
      "command": "npx",
      "args": ["-y", "@apify/actors-mcp-server", "--actors", "malonestar/epa-tsca-cdr-chemical-site-screener"],
      "env": { "APIFY_TOKEN": "YOUR_APIFY_TOKEN" }
    }
  }
}
```

Worked agent prompt:

> Using the EPA TSCA CDR screener, find every US site that manufactures or imports sulfur (CASRN 7704-34-9). For each site, tell me the parent company, the state, and the 2023 production volume — and be explicit about which volumes are confidential rather than treating them as zero.

Chaining: run this actor first to identify the chemical manufacturers near a coordinate, then pass the `epa_frs_id` values to [EPA Contaminated Site Screener](https://apify.com/malonestar/epa-contaminated-site-screener) and [EPA TRI Facility Release Rollup](https://apify.com/malonestar/epa-tri-facility-release-rollup) for the same facilities' regulatory and release history.

### Pricing

Pay per result: **$8 per 1,000 rows** on the standard Free/Bronze/Silver/Gold/Platinum/Diamond tier ladder. One emitted row is one billable result. A run that fails the drift gate emits nothing and bills nothing.

`maxResults` is a hard cap on the total number of rows a run emits, **which is also the hard cap on the bill**. It counts *every* row, including the one `query_summary` row that each asset, chemical or state produces — those are reserved off the top of the cap and the remainder is spent on site-chemical records. It is a whole-run cap, never per scope, so adding assets never multiplies the bill.

Because each scope costs at least one row, `maxResults` must be at least the number of scopes in the request. A run asking for more scopes than its cap fails before emitting anything rather than silently answering only some of them — an asset with no row would be indistinguishable from an asset where nothing was found. `assets` and `chemicalIds` are capped at 100 entries per run and `states` at 60, so the scope count is bounded by the input schema as well as by the code.

Every `query_summary` row publishes the arithmetic: `run_max_results_cap`, `run_scope_summary_rows` and `run_record_row_budget`.

### How it fails

| Situation | Outcome |
|---|---|
| `mode` omitted | HTTP 400 at run creation; the run never starts |
| A mode's companion field is missing or malformed | `Actor.fail` with the exact field named and an example value |
| `www.epa.gov` is unreachable, returns HTML at HTTP 200, or serves a truncated body | `Actor.fail`; **not** a finding of "no chemicals here" |
| A required column, a zip entry or a canary record changes | `Actor.fail` naming the probe |
| The CBI sentinel share collapses | `Actor.fail`; the actor will not publish a volume it cannot vouch for |
| A corroborating probe cannot complete | The run proceeds, `drift_gate_status` reads `verified_degraded`, and `drift_gate_note` says which probe was skipped |
| The query is valid and nothing matched | SUCCESS with a `query_summary` row explaining the coverage |

Set `simulateOutage` to any value other than `none` to exercise a failure path deliberately.

### Source and vintage

`https://www.epa.gov/system/files/other-files/2026-04/2024-cdr-public-csv-data.zip` (14,378,975 bytes, `Last-Modified: Mon, 20 Apr 2026 22:03:49 GMT`), keyless, linked from EPA's [Access CDR Data](https://www.epa.gov/chemical-data-reporting/access-cdr-data) page.

**EPA replaces these files in place under an unchanged URL**, so the vintage is read from `Last-Modified` on every run and published on every row as `source_last_modified` and `source_vintage_status`. Never trust a hard-coded CDR vintage.

### FAQ

**What is TSCA Chemical Data Reporting?** Every four years, manufacturers and importers of TSCA-listed chemicals above a volume threshold must report production volumes, site information and use context to EPA. The 2024 cycle covers calendar years 2020–2023.

**Does this include chemical releases or waste?** No. CDR is manufacture and import. Use the EPA TRI Facility Release Rollup for releases and the EPA RCRA Hazardous Waste Generator Rollup for waste.

**Why is a production volume `null` instead of `0`?** Because the submitter claimed it confidential, reported it as not known, or left it blank. Read `production_volume_2023_status` — a real reported zero is a number, and it is a different fact.

**Why can't I find a site I know files under CDR?** 7,074 records withhold the entire site identity as CBI, and 2,571 more are published at coordinates `0.0 / 0.0`. Query by `chemical` mode to see the confidential-identity records; they carry the substance and volumes without a location.

**Are the production volumes in pounds?** Yes. EPA's own data dictionary states all CDR quantities are reported in pounds; `production_volume_units` says so on every row.

**Why does `workers_exposed_published_label` look wrong?** Because it is. EPA publishes two different label texts for the same band code and writes a literal `?` where a `>=` sign belongs. This actor decodes from the **code**, and republishes EPA's own text verbatim beside it so nothing is hidden.

### Output fields

Every row carries `record_type`. `site_chemical` rows are the reported records; one `query_summary` row per scope carries the coverage the answer depends on.

#### Query scope

| Field | Type | Description |
|---|---|---|
| `record_type` | string or null | site\_chemical for a reported site-chemical record, query\_summary for the one coverage row emitted per query scope. |
| `query_mode` | string or null | The mode this row was produced under: radius, chemical or state. |
| `query_scope_label` | string or null | Label of the scope: the asset label, the CASRN, or the state code. |
| `asset_label` | string or null | Caller-supplied label for the screened coordinate. Null outside radius mode. |
| `asset_latitude` | number or null | Latitude of the screened coordinate. Null outside radius mode. |
| `asset_longitude` | number or null | Longitude of the screened coordinate. Null outside radius mode. |
| `asset_radius_miles` | number or null | Search radius in miles. Null outside radius mode. |
| `distance_miles` | number or null | Great-circle distance from the screened coordinate to the site, in miles. Null when the match was not made on coordinates. |
| `match_basis` | string or null | How this record was matched: coordinate\_radius, chemical\_id, site\_state, or no\_coordinate\_included\_on\_request. |

#### Site identity

| Field | Type | Description |
|---|---|---|
| `site_name` | string or null | Published site name with any trailing asterisk marker removed. Null when EPA withholds the site identity as confidential. |
| `site_name_raw` | string or null | Site name exactly as EPA published it, including the trailing asterisk that appears on 108 rows and is not explained in EPA's own data dictionary. |
| `site_name_has_asterisk_marker` | boolean or null | True when the published site name carries the undocumented trailing asterisk; null when the name is not disclosed. |
| `site_name_status` | string or null | reported, cbi\_withheld, not\_known or not\_reported for the site name. |
| `site_address_line1` | string or null | Street address of the reporting site. Null when withheld or absent. |
| `site_city` | string or null | City of the reporting site. |
| `site_county` | string or null | County or parish of the reporting site. |
| `site_state` | string or null | Two-letter state code of the reporting site. Null when the site identity is confidential. |
| `site_state_status` | string or null | reported, cbi\_withheld, not\_known or not\_reported for the site state. |
| `site_postal_code` | string or null | ZIP code of the reporting site. |
| `site_latitude` | number or null | Latitude EPA published for the site. Null when confidential, absent, or published as the 0/0 sentinel. |
| `site_longitude` | number or null | Longitude EPA published for the site, under the same rules as site\_latitude. |
| `site_coordinate_status` | string or null | disclosed, cbi\_withheld, not\_reported, or null\_island\_sentinel when EPA published exactly 0/0 (2,571 rows nationally). |
| `site_identity_confidential` | boolean or null | True when EPA withheld the whole site identity as CBI, which is why the row carries a chemical but no location. |
| `epa_tsca_program_id` | string or null | EPA TSCA program identifier for the site. |
| `epa_frs_id` | string or null | EPA Facility Registry Service identifier. This is the join key into EPA Contaminated Site Screener and EPA TRI Facility Release Rollup. |
| `site_dun_bradstreet_number` | string or null | Dun & Bradstreet number of the site. |
| `site_naics_code_1` | string or null | Primary NAICS code of the site, split out of the packed code-plus-description string EPA publishes. |
| `site_naics_description_1` | string or null | Primary NAICS description. |
| `site_naics_activity_1` | string or null | The CDR activity EPA associates with the primary NAICS, e.g. Manufacture or Import. |
| `site_naics_code_2` | string or null | Second NAICS code, populated on about a quarter of records. |
| `site_naics_description_2` | string or null | Second NAICS description. |
| `site_naics_code_3` | string or null | Third NAICS code, populated on about a fifth of records. |
| `site_naics_description_3` | string or null | Third NAICS description. |

#### Corporate parentage

| Field | Type | Description |
|---|---|---|
| `standardized_parent_company` | string or null | EPA-standardised parent company name. Null when the corporate block is confidential. |
| `parent_company_status` | string or null | reported, cbi\_withheld, not\_known or not\_reported for the parent company block. |
| `domestic_parent_company` | string or null | Domestic parent company name. |
| `domestic_parent_city` | string or null | Domestic parent company city. |
| `domestic_parent_state` | string or null | Domestic parent company state. |
| `domestic_parent_postal_code` | string or null | Domestic parent company ZIP code. |
| `domestic_parent_dun_bradstreet_number` | string or null | Domestic parent company Dun & Bradstreet number. |
| `foreign_parent_company` | string or null | Foreign parent company name where one is reported. |
| `foreign_parent_city` | string or null | Foreign parent company city. |
| `foreign_parent_country_code` | string or null | Foreign parent company country code. |
| `foreign_parent_dun_bradstreet_number` | string or null | Foreign parent company Dun & Bradstreet number. |
| `has_foreign_parent` | boolean or null | True when EPA published a foreign parent, false when it published the corporate block without one, and null when the corporate block is confidential so foreign ownership is unknown rather than absent. |

#### Chemical identity

| Field | Type | Description |
|---|---|---|
| `chemical_name` | string or null | Systematic chemical substance name as listed on the public portion of the TSCA Inventory. |
| `chemical_id` | string or null | CASRN, or an EPA Accession Number when the specific chemical identity is confidential. |
| `chemical_id_no_dashes` | string or null | The same identifier with dashes stripped, as EPA publishes it. |
| `chemical_id_type` | string or null | CASRN or Accession Number. |
| `chemical_identity_confidential` | boolean or null | True when the identifier is an Accession Number, which per EPA means the chemical identity sits on the confidential portion of the TSCA Inventory. |

#### Reported activity

| Field | Type | Description |
|---|---|---|
| `activity` | string or null | Manufacture, Import or Both. Null when the activity itself is claimed confidential. |
| `activity_status` | string or null | reported, cbi\_withheld, not\_known or not\_reported for the activity. |
| `is_manufacturer` | boolean or null | True when the site manufactures the substance, false when it only imports it, null when the activity is withheld. |
| `is_importer` | boolean or null | True when the site imports the substance, false when it only manufactures it, null when the activity is withheld. |
| `chemical_never_physically_at_site` | boolean or null | True when the submitter reported the substance is never physically at the site (imported and shipped onward). Null when withheld, not known, or not reported. |
| `chemical_never_physically_at_site_status` | string or null | reported, cbi\_withheld, not\_known or not\_reported for the never-at-site question. |

#### Production volumes (CBI-critical)

| Field | Type | Description |
|---|---|---|
| `production_volume_2023_lb` | number or null | Total 2023 production volume in pounds. NULL, never zero, when the value is confidential, not known, or not reported. |
| `production_volume_2023_status` | string or null | reported, cbi\_withheld, not\_known or not\_reported. This is the field that separates a withheld volume from a real zero. |
| `production_volume_2023_cbi_withheld` | boolean or null | True when the 2023 production volume was claimed confidential. |
| `domestic_production_volume_2023_lb` | number or null | Domestically manufactured portion of the 2023 volume, in pounds. |
| `domestic_production_volume_2023_status` | string or null | Disclosure status of the domestic 2023 volume. |
| `import_volume_2023_lb` | number or null | Imported portion of the 2023 volume, in pounds. |
| `import_volume_2023_status` | string or null | Disclosure status of the 2023 import volume. |
| `production_volume_2022_lb` | number or null | 2022 production volume in pounds. |
| `production_volume_2022_status` | string or null | Disclosure status of the 2022 volume. |
| `production_volume_2021_lb` | number or null | 2021 production volume in pounds. |
| `production_volume_2021_status` | string or null | Disclosure status of the 2021 volume. |
| `production_volume_2020_lb` | number or null | 2020 production volume in pounds. |
| `production_volume_2020_status` | string or null | Disclosure status of the 2020 volume. |
| `volume_used_on_site_2023_lb` | number or null | Volume used on site in 2023, in pounds. |
| `volume_used_on_site_2023_status` | string or null | Disclosure status of the volume used on site. |
| `volume_exported_2023_lb` | number or null | Volume exported in 2023, in pounds. |
| `volume_exported_2023_status` | string or null | Disclosure status of the exported volume. |
| `production_volume_units` | string or null | Unit for every volume field. CDR reports all quantities in pounds. |
| `production_volume_disclosure` | string or null | fully\_reported, partially\_reported, partially\_cbi\_withheld, fully\_cbi\_withheld or not\_reported across the four annual volumes. |
| `production_volume_change_2020_to_2023_lb` | number or null | Change in pounds from 2020 to 2023, computed only when both endpoints are genuinely reported. |
| `production_volume_change_2020_to_2023_pct` | number or null | Percentage change from 2020 to 2023, computed only when both endpoints are genuinely reported. |
| `production_volume_trend_status` | string or null | computed, or not\_computable when an endpoint is withheld or missing. |
| `production_volume_trend_note` | string or null | Which endpoint prevented the trend from being computed. |

#### National aggregate context

| Field | Type | Description |
|---|---|---|
| `national_aggregated_pv_2023_text` | string or null | EPA's nationally aggregated 2023 production volume for the substance, exactly as published. It is a BAND on 46,141 of 50,116 rows, not a number. |
| `national_aggregated_pv_2023_low_lb` | number or null | Lower bound of the national band in pounds; equal to the upper bound when EPA published a single number. |
| `national_aggregated_pv_2023_high_lb` | number or null | Upper bound of the national band in pounds. |
| `national_aggregated_pv_2023_is_range` | boolean or null | True when EPA published a band, false when it published a single number, null when it published nothing. |
| `national_aggregated_pv_2023_status` | string or null | Disclosure status of the national 2023 aggregate. |
| `national_aggregated_pv_2022_text` | string or null | National aggregate for 2022 as published. |
| `national_aggregated_pv_2022_status` | string or null | Disclosure status of the national 2022 aggregate. |
| `national_aggregated_pv_2021_text` | string or null | National aggregate for 2021 as published. |
| `national_aggregated_pv_2021_status` | string or null | Disclosure status of the national 2021 aggregate. |
| `national_aggregated_pv_2020_text` | string or null | National aggregate for 2020 as published. |
| `national_aggregated_pv_2020_status` | string or null | Disclosure status of the national 2020 aggregate. |

#### Exposure and physical form

| Field | Type | Description |
|---|---|---|
| `workers_exposed_code` | string or null | EPA band code W1-W8 for workers reasonably likely to be exposed, or CBI/NKRA as published. |
| `workers_exposed_band` | string or null | Plain-language band decoded from the code, not from EPA's published label text. |
| `workers_exposed_low` | number or null | Lower bound of the worker band. |
| `workers_exposed_high` | number or null | Upper bound of the worker band; null for the open-ended top band. |
| `workers_exposed_published_label` | string or null | EPA's own label text, republished verbatim. Note that EPA publishes two different labels for the same code and writes a literal question mark where a greater-than-or-equal sign belongs. |
| `workers_exposed_status` | string or null | reported, cbi\_withheld, not\_known or not\_reported for worker exposure. |
| `max_concentration_code` | string or null | EPA band code M1-M5 for maximum concentration, or CBI/NKRA as published. |
| `max_concentration_band` | string or null | Plain-language concentration band decoded from the code. |
| `max_concentration_low_pct` | number or null | Lower bound of the concentration band in percent. |
| `max_concentration_high_pct` | number or null | Upper bound of the concentration band in percent. |
| `max_concentration_published_label` | string or null | EPA's own concentration label, republished verbatim. |
| `max_concentration_status` | string or null | reported, cbi\_withheld, not\_known or not\_reported for maximum concentration. |
| `percent_byproduct_code` | string or null | EPA band code B1-B4 for percent byproduct. 481 rows carry a raw percentage here instead of a band code. |
| `percent_byproduct_band` | string or null | Plain-language byproduct band decoded from the code. |
| `percent_byproduct_low_pct` | number or null | Lower bound of the byproduct band in percent. |
| `percent_byproduct_high_pct` | number or null | Upper bound of the byproduct band in percent. |
| `percent_byproduct_published_label` | string or null | EPA's own byproduct label, republished verbatim. |
| `percent_byproduct_status` | string or null | reported, cbi\_withheld, not\_known or not\_reported for percent byproduct. |
| `percent_byproduct_code_is_numeric` | boolean or null | True when EPA published a raw percentage in the band-code column rather than a B-code. |
| `physical_forms` | array or null | Physical forms of the substance at the site, split from EPA's semicolon-delimited list. Null when withheld or not reported. |
| `physical_forms_status` | string or null | reported, cbi\_withheld, not\_known or not\_reported for physical form. |
| `physical_form_pct_dry_powder` | number or null | Share of the production volume in dry powder form, as a percentage. Despite EPA naming these columns "PF PPV" they hold percentages, not volumes. |
| `physical_form_pct_pellets_or_large_crystals` | number or null | Share of the production volume as pellets or large crystals, in percent. |
| `physical_form_pct_wet_solid` | number or null | Share of the production volume as water- or solvent-wet solid, in percent. |
| `physical_form_pct_other_solid` | number or null | Share of the production volume as other solid, in percent. |
| `physical_form_pct_gas_or_vapor` | number or null | Share of the production volume as gas or vapor, in percent. |
| `physical_form_pct_liquid` | number or null | Share of the production volume as liquid, in percent. |
| `physical_form_pct_nkra` | number or null | Share of the production volume whose physical form is not known or reasonably ascertainable, in percent. |
| `physical_form_pct_status` | string or null | Disclosure status of the physical-form percentage block. |
| `recycled_or_otherwise_used` | boolean or null | True when the substance is recycled or otherwise used at the site. EPA publishes this in mixed case, which is normalised here. |
| `recycled_or_otherwise_used_status` | string or null | reported, cbi\_withheld, not\_known or not\_reported for the recycling question. |

#### Coverage and denominators

| Field | Type | Description |
|---|---|---|
| `cdr_reporting_cycle` | number or null | CDR submission cycle this file covers. |
| `cdr_principal_reporting_year` | number or null | The reporting year for which site-level volumes are published in full detail. |
| `national_rows_total` | number or null | Total site-chemical records in the national Manufacture-Import table, excluding blank rows. |
| `national_distinct_sites` | number or null | Distinct sites with a disclosed TSCA program identifier nationally. |
| `national_distinct_chemicals` | number or null | Distinct chemical identifiers nationally. |
| `national_site_identity_cbi_rows` | number or null | National count of records whose entire site identity is confidential, and which therefore cannot appear in any location or state query. |
| `national_coordinate_usable_rows` | number or null | National count of records with a usable coordinate. |
| `national_coordinate_null_island_rows` | number or null | National count of records published at exactly 0/0. |
| `national_coordinate_missing_rows` | number or null | National count of records with a blank or unparseable coordinate. |
| `national_coordinate_usable_share_pct` | number or null | Share of national records carrying a usable coordinate, after excluding confidential identities and the 0/0 sentinel. |
| `national_pv_2023_cbi_rows` | number or null | National count of confidential 2023 production volumes. |
| `national_pv_2023_reported_rows` | number or null | National count of disclosed 2023 production volumes. |
| `national_pv_2023_not_reported_rows` | number or null | National count of blank 2023 production volumes. |
| `national_pv_2023_not_known_rows` | number or null | National count of 2023 production volumes reported as not known or reasonably ascertainable. |
| `national_pv_2023_cbi_share_pct` | number or null | Share of national records whose 2023 production volume is confidential. |
| `scope_matched_rows_total` | number or null | How many records matched this scope before the maxResults cap. |
| `scope_rows_emitted` | number or null | How many records were emitted for this scope after the cap. |
| `scope_results_truncated` | boolean or null | True when the maxResults cap removed matching records. |
| `scope_rows_invisible_to_location_screen` | number or null | Records excluded from this radius screen because EPA published no usable coordinate for them. |
| `scope_rows_with_site_identity_cbi` | number or null | Matched records whose site identity is confidential, so they carry a chemical but no location. |
| `scope_distinct_sites` | number or null | Distinct sites emitted for this scope. |
| `scope_distinct_chemicals` | number or null | Distinct chemicals emitted for this scope. |
| `run_max_results_cap` | number or null | The maxResults value this run was given. It is the hard cap on total emitted rows, and therefore on the bill, counting query\_summary rows as well as site-chemical rows. |
| `run_scope_summary_rows` | number or null | How many rows of the maxResults cap were reserved for query\_summary rows - exactly one per asset, chemical or state in the request. This row is one of them. |
| `run_record_row_budget` | number or null | The rows left for site-chemical records after the query\_summary rows were reserved: maxResults minus run\_scope\_summary\_rows. |
| `source_blank_rows_skipped` | number or null | Completely blank rows present in EPA's CSV and skipped, counted rather than silently dropped. |
| `coverage_note` | string or null | Plain-language statement of what this query could and could not see, so an empty result is not read as an absence of chemical activity. |

#### Provenance and per-source status

| Field | Type | Description |
|---|---|---|
| `source_file_url` | string or null | The EPA bulk file this run read. |
| `source_last_modified` | string or null | Last-Modified header of the bulk file, read on every run because EPA replaces these files in place under an unchanged URL. |
| `source_content_length_bytes` | number or null | Size in bytes of the bulk file this run read. |
| `source_vintage_status` | string or null | current, stale\_over\_4\_years, future\_dated, unparseable or unknown, derived from Last-Modified. |
| `retrieved_at` | string or null | UTC timestamp when this run fetched the file. |
| `cdr_bulk_file_status` | string or null | Per-source status for the EPA bulk file. |
| `cdr_manufacture_import_status` | string or null | Per-source status for the Manufacture-Import table. |
| `drift_gate_status` | string or null | verified when every probe passed, or verified\_degraded when a corroborating probe could not complete and the run proceeded. |
| `drift_probes_verified` | number or null | How many live drift probes passed before any row was billed. |
| `drift_probes_unavailable` | number or null | How many drift probes could not complete. |
| `drift_gate_note` | string or null | Which corroborating probes could not complete, when the gate ran degraded. |
| `source_row_index` | number or null | Line number of this record in EPA's CSV, for reproducibility. |

# Actor input Schema

## `mode` (type: `string`):

How to query the 2024 TSCA Chemical Data Reporting file. 'radius' screens coordinates and returns every CDR site within the radius; 'chemical' maps every US site that manufactures or imports a given CASRN; 'state' lists a state's CDR filers. Required, so a bare {} call is rejected before the run starts and nothing is billed.

## `assets` (type: `array`):

Coordinates to screen. Each entry: {"lat": number, "lon": number, "radiusMiles": number, "label": string}. radiusMiles defaults to 5 in code (never a schema default, which Apify injects server-side) and is capped at 100. Maximum 100 assets per run: each asset emits one query\_summary row inside the maxResults budget, so maxResults must be at least the number of assets. Used only when mode = radius.

## `chemicalIds` (type: `array`):

CASRNs to reverse-map, with or without dashes, e.g. \["7704-34-9"] for Sulfur. Every US site reporting manufacture or import of the substance is returned, including sites whose identity EPA withholds as CBI. Maximum 100 per run: each chemical emits one query\_summary row inside the maxResults budget. Used only when mode = chemical.

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

Two-letter postal codes, e.g. \["CO"]. Rows whose site state is claimed confidential cannot be assigned to any state and are therefore absent from every state total. Maximum 60 per run: each state emits one query\_summary row inside the maxResults budget. Used only when mode = state.

## `chemicalFilter` (type: `array`):

Optional CASRN list applied on top of any mode, so a radius or state query can be narrowed to specific substances. Leave empty to return every chemical reported at the matched sites.

## `siteQuery` (type: `string`):

Optional case-insensitive substring match on the published site name. Rows whose site name is claimed confidential can never match a name filter and are excluded when this is set.

## `includeSitesWithoutCoordinates` (type: `boolean`):

In radius mode, also emit records whose coordinates are blank or published as the 0/0 null-island sentinel, flagged with site\_coordinate\_status and a null distance. They cannot be confirmed to be inside the radius, so this is off unless requested.

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

The hard cap on total rows this run emits, which is also the hard cap on the bill. It counts EVERY emitted row, including the one query\_summary row each asset, chemical or state produces: those are reserved off the top and the remainder is spent on site-chemical records. It is a whole-run cap, never per scope. Applied in code (200 when omitted) rather than as a schema default, so an API or MCP caller who omits it cannot be billed for a national dump. Must be at least the number of scopes in the request. Maximum 5000.

## `simulateOutage` (type: `string`):

Diagnostic seam that forces one drift-gate failure path so the fail-closed behaviour can be re-proved on demand instead of only during a real outage. Leave as 'none' for normal use; any other value makes the run fail and bill nothing.

## Actor input object example

```json
{
  "mode": "radius",
  "assets": [
    {
      "lat": 40.8249,
      "lon": -111.9238,
      "radiusMiles": 3,
      "label": "Salt Lake City refinery corridor"
    },
    {
      "lat": 29.99435,
      "lon": -90.40726,
      "radiusMiles": 3,
      "label": "Norco LA petrochemical corridor"
    }
  ],
  "maxResults": 150,
  "simulateOutage": "none"
}
```

# Actor output Schema

## `results` (type: `string`):

The default dataset.

# 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 = {
    "mode": "radius",
    "assets": [
        {
            "lat": 40.8249,
            "lon": -111.9238,
            "radiusMiles": 3,
            "label": "Salt Lake City refinery corridor"
        },
        {
            "lat": 29.99435,
            "lon": -90.40726,
            "radiusMiles": 3,
            "label": "Norco LA petrochemical corridor"
        }
    ],
    "maxResults": 150,
    "simulateOutage": "none"
};

// Run the Actor and wait for it to finish
const run = await client.actor("malonestar/epa-tsca-cdr-chemical-site-screener").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 = {
    "mode": "radius",
    "assets": [
        {
            "lat": 40.8249,
            "lon": -111.9238,
            "radiusMiles": 3,
            "label": "Salt Lake City refinery corridor",
        },
        {
            "lat": 29.99435,
            "lon": -90.40726,
            "radiusMiles": 3,
            "label": "Norco LA petrochemical corridor",
        },
    ],
    "maxResults": 150,
    "simulateOutage": "none",
}

# Run the Actor and wait for it to finish
run = client.actor("malonestar/epa-tsca-cdr-chemical-site-screener").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 '{
  "mode": "radius",
  "assets": [
    {
      "lat": 40.8249,
      "lon": -111.9238,
      "radiusMiles": 3,
      "label": "Salt Lake City refinery corridor"
    },
    {
      "lat": 29.99435,
      "lon": -90.40726,
      "radiusMiles": 3,
      "label": "Norco LA petrochemical corridor"
    }
  ],
  "maxResults": 150,
  "simulateOutage": "none"
}' |
apify call malonestar/epa-tsca-cdr-chemical-site-screener --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,malonestar/epa-tsca-cdr-chemical-site-screener"
        }
    }
}

```

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/pH8FiaX3cGQY71ysG/builds/CSEJGqp8HiiFlMjLb/openapi.json
