EPA Nonattainment Air Permit Screener — NSR/PSD Siting Risk avatar

EPA Nonattainment Air Permit Screener — NSR/PSD Siting Risk

Pricing

from $4.40 / 1,000 results

Go to Apify Store
EPA Nonattainment Air Permit Screener — NSR/PSD Siting Risk

EPA Nonattainment Air Permit Screener — NSR/PSD Siting Risk

EPA nonattainment air-permit screener. Batch point-in-polygon check of coordinates against 11 EPA Clean Air Act NAAQS pollutant standards: returns nonattainment/maintenance status, area name, classification (Marginal-Extreme), Federal Register citation, and NSR/PSD permitting-risk flag per site.

Pricing

from $4.40 / 1,000 results

Rating

0.0

(0)

Developer

Kyle Maloney

Kyle Maloney

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

10 days ago

Last modified

Share

EPA Clean Air Act nonattainment screener. Give it a list of coordinates and it runs a server-side point-in-polygon check against 11 EPA National Ambient Air Quality Standards (NAAQS) pollutant layers — Ozone (1997/2008/2015 standards), Lead, SO₂, PM2.5 (three standards), PM10, CO and NO₂ — and returns, for each point: whether it sits inside an active Nonattainment area, an EPA-tracked Maintenance area, or an area designated under a revoked NAAQS; the matched area name(s); the classification severity (Marginal → Moderate → Serious → Severe-15 → Severe-17 → Extreme); the Federal Register designation citation and URL; the statutory attainment date; the published 10-year design-value series; and a structured NNSR-vs-PSD permitting pathway with the CAA §182 major-source threshold and offset ratio.

One clean, billable row per asset — including for a clear site outside every designated area, which is a useful and valid result.

This is a screening tool, not a permitting determination. It tells you which Clean Air Act preconstruction-review pathway a major stationary source would fall under at a coordinate. Actual applicability depends on source category, potential-to-emit and the approved State Implementation Plan. Confirm with the state permitting agency or EPA Regional Office.


What changed in v1.1 (and why it matters)

v1.0 shipped three defects that produced confident wrong answers on billable rows while the run reported SUCCESS. All are fixed; no field was removed or renamed.

  1. Severe areas ranked below Marginal. worst_classification recognised only five exact strings. EPA never writes the bare word "Severe" — it writes Severe 15, Severe-15, Severe-17, Subpart 2/Severe 15. Every one of those scored 0. Houston and Dallas both reported Serious when they are Severe 15 for the 2008 ozone NAAQS. Under CAA §182 that is the difference between a 50 tpy and a 25 tpy major-source threshold, and 1.2:1 vs 1.3:1 offsets.
  2. A third of records fell through both status buckets. current_status has four values, not two: EPA also publishes Nonattainment (NAAQS revoked) and Maintenance (NAAQS revoked), and 192 of the 614 screened records (31.3%) carry one. v1.0 matched on strict string equality, so those records set clear: false while flagging nothing and emitting an empty note. They now report through in_revoked_standard_area, pollutants_revoked_standard and verdict: "revoked_standard_only".
  3. A dead layer produced a billable "clear". A per-layer fetch failure was swallowed into "no match found". Every layer now reports status_<layer> on the row, and any unanswered layer nulls clear, in_nonattainment, nsr_psd_permitting_flag, verdict and permitting_pathway rather than rendering them negative. If every asset fails, the run fails and nothing is billed.

The actor also now runs 26 live drift probes against the EPA service before producing a single row — layer numbering, the revoked-layer identity, per-layer record counts, the status and classification vocabularies, positive canaries (Houston, Los Angeles), a negative control (Wichita), coordinate-system magnitude and envelope, and a truncation guard read from the service's own maxRecordCount. If any probe fails, the run fails and nothing is billed.

Behaviourally breaking for existing pipelines (read this if you automate on v1.0 output)

All three fixes above correct wrong answers, and no field was removed or renamed. Two of them nevertheless change values you may be keying on, so a downstream step that does string equality or a boolean filter will behave differently on the same coordinate:

Fieldv1.0v1.1Example
worst_classificationSeriousSevere 15Dallas TX (32.7767, -96.7970) and Houston TX (29.7604, -95.3698), 2008 ozone NAAQS. v1.0 scored every Severe* string as 0, i.e. below Marginal.
in_nonattainmenttruefalseClarksville TN (36.5298, -87.3595). The area is designated only under a revoked NAAQS, so it is not an active nonattainment area for new-source review. It now reports in_revoked_standard_area: true and verdict: "revoked_standard_only".

Both new values are the correct ones. If you match on classification strings, switch to worst_classification_tier (a normalised, sortable vocabulary) or worst_classification_rank (numeric) instead of the raw EPA string, which EPA writes several ways for the same tier. If you filter on in_nonattainment, add in_revoked_standard_area to keep the anti-backsliding cases you used to catch by accident.

Also note the doctrine contract now in force on every screen-integrity field: null means not checked, false means checked and negative. A filter written as !row.in_nonattainment will now also pass rows where the screen was incomplete. Use row.in_nonattainment === false.


Who it's for

  • Air-permit / NSR-PSD consultants — screen a shortlist of candidate sites for nonattainment exposure before scoping a permit application, with the classification tier and offset ratio already resolved.
  • Industrial, power-plant and data-center siting teams — flag the single biggest hidden air-permitting cost gate before committing to a location, including sites just outside a boundary.
  • EHS and compliance teams — batch-check an existing facility portfolio against every current and revoked NAAQS designation, with the Federal Register citation for each.
  • EPCs and project developers — screen a multi-site portfolio for classification severity, offset requirements and redesignation risk in one call.
  • Phase-I ESA / environmental due-diligence — add an authoritative nonattainment data point to a site assessment.
  • GIS & data teams / AI agents — enrich any coordinate list with EPA nonattainment status without hosting the polygon layers yourself.

Example input

{
"assets": [
{ "lat": 29.7604, "lon": -95.3698, "label": "Houston, TX" },
{ "lat": 34.0522, "lon": -118.2437, "label": "Los Angeles, CA" },
{ "lat": 36.4971, "lon": -87.3829, "label": "Clarksville, TN" },
{ "lat": 33.8659, "lon": -84.4032, "label": "Atlanta, GA" },
{ "lat": 29.7000, "lon": -96.3500, "label": "Colorado County, TX" },
{ "lat": 37.6872, "lon": -97.3301, "label": "Wichita, KS" }
],
"includeMaintenance": true,
"nearbyRadiusKm": 40,
"maxAssets": 500
}
Siteverdictworst_classificationnsr_major_source_threshold_tpypermitting_pathway
Houston, TXnonattainmentSevere 1525NNSR
Los Angeles, CAnonattainmentExtreme10NNSR
Clarksville, TNrevoked_standard_onlynullnullPSD
Atlanta, GAmaintenance_onlynullnullPSD
Colorado County, TXclearnullnullPSD
Wichita, KSclearnullnullPSD

Colorado County is the interesting row: it is genuinely clear, but with nearbyRadiusKm: 40 it reports near_boundary_flag: true and nearest_area_distance_km: 26.73 to the Houston-Galveston-Brazoria boundary -- a redesignation-risk signal a pure in/out check would miss.

Input options

FieldTypeDefaultMeaning
assetsarrayrequired[{ lat, lon, label }] in WGS84 decimal degrees. One billable row per asset.
pollutantsarray[] (all 11)Restrict to specific standard keys, e.g. ["ozone_8hr_2015","pm25_annual_2012"]. Excluded standards report status_<key>: "not_queried".
includeMaintenancebooleantrueSet false to report only nonattainment designations and drop every maintenance match, including revoked-NAAQS maintenance areas.
nearbyRadiusKminteger0Set above 0 to also report designated areas within N km that the point is outside of. Populates the nearby_* fields; leave at 0 and they read null (= not checked).
maxAssetsinteger500Safety cap, max 2000.

Valid pollutants keys: ozone_8hr_1997, ozone_8hr_2008, ozone_8hr_2015, lead_2008, so2_1hr_2010, pm25_24hr_2006, pm25_annual_1997, pm25_annual_2012, pm10_1987, co_1971, no2_1971.


Output fields — one row per asset

Identity

FieldTypeDescription
asset_labelstringYour label, or asset_N if omitted.
asset_latnumberScreened latitude (WGS84).
asset_lonnumberScreened longitude (WGS84).
checked_atstringUTC timestamp of the run.
source_urlstringThe EPA OAR FeatureServer used.
disclaimerstringInformational-use disclaimer.

Headline verdict

FieldTypeDescription
verdictstringnonattainment · maintenance_only · revoked_standard_only · clear. null whenever any contributing layer did not answer — it can never read clear on an incomplete screen.
in_nonattainmentbooleanInside an active nonattainment area under a currently enforceable NAAQS. false = checked and negative; null = not checked.
in_maintenancebooleanInside an EPA maintenance-plan area under a currently enforceable NAAQS.
in_revoked_standard_areabooleanInside an area designated under a revoked NAAQS. Not enforced for new-source review, but SIP anti-backsliding obligations may still apply.
clearbooleanOutside every designated area for every screened standard.
nsr_psd_permitting_flagbooleanTrue when in_nonattainment is true.
notestringPlain-English one-line summary. Never empty on a billable row.

Classification severity

FieldTypeDescription
worst_classificationstringThe raw EPA string of the most severe active nonattainment match, e.g. Severe 15.
worst_classification_tierstringNormalised tier — Transitional, Sub-marginal, Marginal, Moderate, Serious, Severe, Severe-15, Severe-17, Extreme. Sort and filter on this.
worst_classification_ranknumberSeverity 0.25–5 (Marginal 1, Moderate 2, Serious 3, Severe-15 4, Severe-17 4.5, Extreme 5).
worst_classification_pollutantstringWhich standard produced the worst classification.
worst_classification_areastringWhich EPA area produced it.
worst_classification_basisstringOne-sentence audit trail for the headline severity.

Permitting implication (informational)

FieldTypeDescription
permitting_pathwaystringNNSR (Nonattainment New Source Review) or PSD (Prevention of Significant Deterioration).
permitting_pathway_basisstringWhy that pathway was reported.
nsr_major_source_threshold_tpynumberCAA §182 major stationary source threshold in tpy of VOC/NOx for the worst ozone classification (Marginal/Moderate 100, Serious 50, Severe 25, Extreme 10). null for non-ozone pollutants, which have no statutory tier table.
nsr_offset_ratiostringCAA §182 minimum offset ratio (1.1:1 → 1.5:1). The SIP may require more.
nsr_threshold_basisstringWhich ozone classification and area the threshold came from. §182 tiers are ozone-only while worst_classification covers every pollutant, so when they disagree — Phoenix is Serious for PM-10 but Moderate for ozone, giving 100 tpy — this field says so instead of leaving two numbers to be reconciled.

Designations found

FieldTypeDescription
pollutants_nonattainmentarrayEPA pollutant_name of every active nonattainment standard hit.
pollutants_maintenancearrayEvery active maintenance standard hit.
pollutants_revoked_standardarrayEvery revoked-NAAQS standard hit.
matched_area_countintegerDistinct designated polygons containing the point.
active_designation_countintegerOf those, how many are under a currently enforceable NAAQS.
revoked_designation_countintegerOf those, how many are under a revoked NAAQS.
matched_areasarrayFull 52-field detail per match — see below.

Screen integrity (doctrine: null = not checked, false = checked and negative)

FieldTypeDescription
screen_completebooleanTrue only when every requested layer answered.
layers_queried_countintegerLayers screened for this asset (0 for an invalid coordinate).
layers_ok_countintegerLayers that returned a usable response.
layers_failed_countintegerLayers that did not answer. Any value above 0 nulls every verdict field.
layers_failedarrayKeys of the failed layers.
layers_failed_detailarraykey:outcome strings.
errorstringMessage for an invalid coordinate or an incomplete screen.
error_kindstringinvalid_input or upstream_unavailable.
status_ozone_8hr_1997stringPer-layer outcome: ok · unavailable · not_queried.
status_ozone_8hr_2008stringPer-layer outcome.
status_ozone_8hr_2015stringPer-layer outcome.
status_lead_2008stringPer-layer outcome.
status_so2_1hr_2010stringPer-layer outcome.
status_pm25_24hr_2006stringPer-layer outcome.
status_pm25_annual_1997stringPer-layer outcome.
status_pm25_annual_2012stringPer-layer outcome.
status_pm10_1987stringPer-layer outcome.
status_co_1971stringPer-layer outcome.
status_no2_1971stringPer-layer outcome.

Near-boundary (populated only when nearbyRadiusKm > 0)

FieldTypeDescription
nearby_radius_kmnumberThe radius used. null = the proximity pass was not requested.
nearby_area_countintegerDesignated areas within the radius that the point is outside of.
nearest_area_distance_kmnumberDistance to the nearest such boundary, from geometry generalized to ~0.002° (≈200 m).
nearest_area_namestringEPA name of that nearest area.
nearest_area_pollutantstringStandard under which it is designated.
near_boundary_flagbooleanTrue when at least one area is within the radius.
nearby_areasarraySame 52-field detail as matched_areas, sorted nearest first, each with contains_site: false and distance_km.

Inside matched_areas / nearby_areas — 52 fields per match

pollutant_key, pollutant, area_name, state_name, status (raw EPA string), status_normalized (Nonattainment/Maintenance), status_recognized, naaqs_revoked, classification (raw), classification_tier, classification_rank, classification_recognized, naaqs_standard, layer_id, objectid, composite_id, epa_region, epa_region_office, designation_citation, designation_url, designation_pub_date, designation_effective_date, statutory_attainment_date, classification_citation, classification_effective_date, classification_url, design_value, design_value_units, design_value_series (the published 10-year series), design_value_prior_year, design_value_trend (improving/worsening/flat), original_design_value, original_design_value_24hr, original_design_value_years, clean_data_determination_effective_date, clean_data_determination_citation, clean_data_determination_url, attainment_determination_effective_date, attainment_determination_citation, attainment_determination_url, redesignation_proposed_pub_date, redesignation_effective_date, redesignation_citation, redesignation_url, projected_redesignation_date, population_2010, population_2020, population_current, nsr_major_source_threshold_tpy, nsr_offset_ratio, contains_site, distance_km.

A single point can match more than one standard — Los Angeles hits four. The flat row rolls up the worst classification and every pollutant name; matched_areas lists each match individually.


Which inputs populate the sparser fields

Some EPA fields are genuinely sparse. These coordinates populate them, and each is pinned by an offline fixture so it cannot silently rot:

FieldPopulating inputWhat you get
attainment_determination_*Atlanta, GA 33.8659, -84.4032DAA 87 FR 60897, effective 2022-11-07 (live non-null rate 30.6%)
projected_redesignation_dateChicago, IL 41.8781, -87.62982022-09-30 under the 2008 ozone NAAQS. EPA writes 2001-01-01 as a null placeholder on 746 of 792 records (94.2%); this actor maps that sentinel to null rather than emitting a fabricated date
clean_data_determination_*Los Angeles, CA 34.0522, -118.2437CDD records on the PM2.5 designations (live non-null rate 25.4%)
nearby_*Colorado County, TX 29.7000, -96.3500 with nearbyRadiusKm: 403 nearby areas, nearest 26.73 km
error, error_kind, layers_failed, layers_failed_detail(none — by design)These are null on every healthy run. That is good news, not a dead column: they populate only when a coordinate is unusable or an EPA layer does not answer

Pricing

$8 per 1,000 result rows (Pay-per-event, one automatic result event per asset row). A 250-site portfolio screen across all 11 NAAQS standards costs $2.00. Graduated paid-plan discounts apply automatically (Bronze −20% → Diamond −70%). Failed runs bill nothing: the drift gate and the billing gate both fire before any row is written.

Use as an MCP tool

This actor is callable by AI agents (Claude, Cursor, etc.) via mcp.apify.com. Its typed input (assets[]{lat,lon,label}) and field-level output descriptions make it a clean, chainable "is this coordinate in an EPA nonattainment area, and does it trigger NNSR instead of PSD?" tool for siting, permitting and environmental-diligence agents. Agents should read verdict and screen_complete together — a null verdict means the screen was incomplete, not that the site is clear.

FAQ

Is my site in an EPA nonattainment area? Pass its coordinate and read verdict, in_nonattainment, pollutants_nonattainment and matched_areas.

Does my project need Nonattainment New Source Review? permitting_pathway reports NNSR whenever the point is inside an active nonattainment area for any screened pollutant, and nsr_major_source_threshold_tpy / nsr_offset_ratio give the CAA §182 numbers for that classification. This is a screen: NNSR applies only to major new or modified stationary sources, so applicability also depends on source category and potential-to-emit, and the approved SIP may be more stringent. Confirm with the state permitting agency.

What does the classification mean? For pollutants with tiered classifications (mainly ozone, plus some PM standards), it drives the major-source threshold and the offset ratio. EPA publishes compound values such as Severe 15 and Subpart 2/Moderate; worst_classification returns the string verbatim and worst_classification_tier / worst_classification_rank give a stable sortable form. Lead, SO₂, NO₂ and CO have no tiered classification — worst_classification is null there even when in_nonattainment is true, and worst_classification_basis says so.

What is a "revoked NAAQS" designation? EPA revoked the 1-hour ozone (1979), 8-hour ozone (1997) and, for most areas, the annual PM2.5 (1997) standards. Their designations remain published with a (NAAQS revoked) status — 31.3% of the records this actor screens. They do not drive new-source review, but Clean Air Act anti-backsliding obligations in the SIP can survive revocation. They are reported separately in in_revoked_standard_area and pollutants_revoked_standard and never counted as enforceable nonattainment.

Nonattainment vs maintenance? Nonattainment means the area currently fails the NAAQS. Maintenance means it has attained the standard but remains under an EPA-approved maintenance plan, so a redesignation-to-nonattainment risk still exists. Set includeMaintenance: false to screen only active nonattainment.

Why is clear sometimes null instead of false? Because the screen was incomplete. null means "not checked" and false means "checked and negative" — a layer that never answered cannot rule anything out, so the actor refuses to render a negative it did not verify. Check screen_complete, layers_failed and error_kind.

How current is this data? The EPA OAR source layer is refreshed weekly from an internal OAQPS database and is the same authoritative source behind EPA's Green Book. Every run re-reads the service metadata and re-verifies the layer set, record counts and vocabularies before screening.

Source

EPA Office of Air and Radiation (OAR) Nonattainment Areas and Designations ArcGIS FeatureServer — 12 polygon layers, of which 11 are screened. Layer 10 (1-hour ozone, 1979) is excluded because its layer name declares the standard revoked; note that layers 0 and 6 also carry revoked-status records, which are reported per record rather than dropped.

https://services.arcgis.com/cJ9YHowT8TU7DUyn/arcgis/rest/services/Nonattainment_Areas_and_Designations/FeatureServer