# Pennsylvania Radon - Test Results & Mitigation Systems (`j0401/pa-radon`) Actor

Pennsylvania home radon data (public open data): 3.33M test results (1985-2026) with the reading in pCi/L, county, municipality, building type, floor tested and test method; plus 523k mitigation systems with the remediation installed and its cost. Filter by county, floor, method or level.

- **URL**: https://apify.com/j0401/pa-radon.md
- **Developed by:** [Wenhao Yang](https://apify.com/j0401) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.03 / 1,000 pennsylvania radon records

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/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

## Pennsylvania Radon - Test Results & Mitigation Systems

Two Pennsylvania radon registers from the Department of Environmental
Protection, published as public open data on data.pa.gov - every recorded radon
measurement (homes, schools and non-residential buildings), and every
mitigation system installed to fix one.

### Low cost

**From $0.00005 per record, down to $0.00003 at Gold.** Pay only for the
records you actually receive - the whole register stays queryable, you are
never billed for the scan behind it.

### What you get

Pick a register with `corpus`:

#### `corpus=testing` (default) - radon test results

**3.33 million measurements**, from 1986 through July 2026. 99.9998% carry a
real numeric reading in pCi/L.

- **Where** - county, municipality, ZIP
- **What building** - purpose (Residential / School / Non-Residential),
  building type and sub-type
- **The test** - the floor tested, the test method, and the two test dates
- **The result** - the reading in **pCi/L**

#### `corpus=mitigation` - mitigation systems

**~527k systems**, through October 2026.

- **Where** - county, municipality, ZIP and coordinates (the coordinates are
  county-level centroids, not site-level locations)
- **What building** - purpose, building type, floor tested
- **The fix** - the remediation system installed, whether the test was
  pre- or post-mitigation, and the **system cost**
- **The reading** - the radon level at that system's test

#### The two registers cannot be joined

Their identifiers live in different namespaces - a testing row carries a
`radon_data_identifier` in roughly the 5-11 digit range plus a small integer
result id; a mitigation row carries a much shorter `dataidentifier` and a
formatted result id (`31646-0584-1-2`). No key is shared between the two
registers. They are offered side by side as two ends of one story - a
measurement and the fix installed after it - never as linkable.

### Filter it

- **County, municipality, ZIP** - `county="Allegheny"`,
  `municipality="Pittsburgh"`, `postalCode="19148"`.
- **Radon level** - `minValue=4` finds every result at or above the EPA action
  level of 4 pCi/L (1,123,051 of 3.33M test results).
- **Floor, building purpose, test method**.
- **Remediation** - `remediationSystem="Sub-Slab Ventilation"`, `minCost=1500`
  (mitigation register).
- **Date range** on the test end date, and **last-N-days**.

### Example inputs

**Tests at or above the EPA action level** - the headline query.

```json
{ "corpus": "testing", "minValue": 4, "maxResults": 50 }
```

**One county's radon picture** - filter and aggregate in one register.

```json
{ "corpus": "testing", "county": "Montgomery", "mode": "aggregate", "groupBy": "buildingType" }
```

**What mitigation costs** - the systems installed, and their cost.

```json
{ "corpus": "mitigation", "minCost": 1500, "maxResults": 50 }
```

### Or roll it up

Set `mode=aggregate` for a rolled-up count by **county**, **building
purpose**, **building type**, **floor tested**, **test method**,
**remediation system** or **device type**. Dimensions differ per register;
`remediationSystem`/`deviceType` are mitigation-only and `testMethod` is
testing-only (the mitigation register stores a bare equipment code with no
legend, so that field is left blank there). Every group comes back - not cut
off by `maxResults`.

### Then follow the money and the risk

- Every home radon reading in a county, and the share above 4 pCi/L.
- Which remediation systems are installed where, and at what cost.
- Radon by building type, floor and test method - basement vs. first floor.
- The mitigation record for a ZIP code, with county-level coordinates.

### Example output

**One mitigation system** - `corpus=mitigation`, `county=Allegheny`:

```json
{
 "platform": "pa-radon",
 "source": "pa-radon-mitigation",
 "mode": "rows",
 "groupKey": "",
 "groupCount": "",
 "groupBy": "",
 "dataIdentifier": "517274",
 "testResultIdentifier": "372377-3322-1-2",
 "county": "Allegheny",
 "municipality": "IMPERIAL",
 "postalCode": "15126",
 "buildingPurpose": "Residential",
 "buildingType": "2-STORY",
 "buildingSubType": "",
 "floorTested": "BASEMENT",
 "testMethod": "",
 "measureValue": "0.5",
 "testStartDate": "",
 "testEndDate": "2026-06-20T00:00:00.000",
 "dataSource": "Pennsylvania Department of Environmental Protection",
 "deviceType": "",
 "remediationSystem": "ACTIVATE A PASSIVE SYSTEM",
 "mitigationTestType": "POST",
 "mitigationYear": "2026",
 "systemCost": "775",
 "latitude": "40.46735543",
 "longitude": "-79.98619843",
 "corpus": "mitigation",
 "sourceUpdatedAt": "2026-10-13"
}
```

**`mode=aggregate`, `groupBy=county`** (testing) - one count row per county:

```
Montgomery                      391,126
Allegheny                       356,402
Bucks                           292,080
Chester                         261,523
Delaware                        193,590
Philadelphia                    193,339
```

### Notes on the data, from the source

- **Readings are in pCi/L** (picocuries per litre). The EPA action level is
  4 pCi/L; the value is returned exactly as measured.
- **Two registers, not one table.** See above - they cannot be joined.
- **Mitigation coordinates are county-level centroids.** The source carries only
  67 distinct points for ~527k systems, so use `county` for geography; the
  coordinates do not identify a site.
- **`deviceType` is sparse on the mitigation register** - it is recorded on about
  a quarter of rows, so its roll-up returns a large `(blank)` group.
- **Two columns in the testing register are dead and are not exposed.** The
  mitigation-system indicator holds a single value across all 3.33M rows, and
  the census tract is filled on 6.6% of rows. Exposing either would promise a
  filter that cannot narrow anything.
- **"Not Collected" and "Unknown" are values the source publishes**, not
  missing data.
- **The registers are refreshed annually** (the State's own update frequency),
  not daily; `sourceUpdatedAt` carries the dataset's own refresh date.

# Actor input Schema

## `corpus` (type: `string`):

Which register: 'testing' = radon test results (3.33M rows, default), 'mitigation' = mitigation systems installed (523k rows). The two cannot be joined.

## `county` (type: `string`):

Substring match on the county name, e.g. 'Allegheny', 'Montgomery', 'Philadelphia'. Blank = any.

## `municipality` (type: `string`):

Substring match on the municipality, e.g. 'PITTSBURGH', 'PHILA'. Blank = any.

## `postalCode` (type: `string`):

Substring match on the postal code, e.g. '19148'. Blank = any.

## `floor` (type: `string`):

Substring match on the floor tested, e.g. 'BASEMENT', 'FIRST FLOOR', 'SLAB ON GRADE'. Blank = any.

## `buildingPurpose` (type: `string`):

Exact building purpose. 'Not Collected' and 'Unknown' are values the source itself publishes ('Unknown' only in the mitigation corpus). Blank = any.

## `testMethod` (type: `string`):

Exact radon test method (testing register only). Blank = any.

## `remediationSystem` (type: `string`):

Substring match on the mitigation system installed, e.g. 'SUB-SLAB VENTILATION', 'SEALING' (mitigation register only). Blank = any.

## `deviceType` (type: `string`):

Substring match on the device type name of the mitigation test (mitigation register only). Blank = any.

## `testFrom` (type: `string`):

Earliest test-end date, YYYY-MM-DD. Blank = any.

## `testTo` (type: `string`):

Latest test-end date, YYYY-MM-DD. Blank = any.

## `minValue` (type: `number`):

Smallest radon reading in pCi/L. Set 4 to find results at or above the EPA action level. Blank = no lower bound.

## `maxValue` (type: `number`):

Largest radon reading in pCi/L. Blank = no upper bound.

## `minCost` (type: `number`):

Smallest mitigation system cost in dollars (mitigation register only). Blank = no lower bound.

## `maxCost` (type: `number`):

Largest mitigation system cost in dollars (mitigation register only). Blank = no upper bound.

## `recentDays` (type: `integer`):

Only rows whose test-end date falls within the last N days. Ignored when 'Test end on or after' is set. 0 = off.

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

rows = records matching your filters (default). aggregate = one count row per group (see groupBy).

## `groupBy` (type: `string`):

Which dimension to aggregate over (mode=aggregate). Blank = county. Dimensions differ per register - 'remediationSystem' and 'deviceType' are mitigation only, 'testMethod' is testing only. Every group is returned.

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

Cap the number of records pushed in rows mode (0 = default 50; up to 2,000 per run). Each record is metered individually, so there is no per-run charge cap. Aggregate mode returns every group.

## Actor input object example

```json
{
  "corpus": "testing",
  "county": "",
  "municipality": "",
  "postalCode": "",
  "floor": "",
  "buildingPurpose": "",
  "testMethod": "",
  "remediationSystem": "",
  "deviceType": "",
  "testFrom": "",
  "testTo": "",
  "recentDays": 0,
  "mode": "rows",
  "groupBy": "",
  "maxResults": 50
}
```

# Actor output Schema

## `recordsUrl` (type: `string`):

Pennsylvania radon test and mitigation records - as JSON

## `datasetUrl` (type: `string`):

No description

## `runUrl` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("j0401/pa-radon").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("j0401/pa-radon").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 '{}' |
apify call j0401/pa-radon --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,j0401/pa-radon"
        }
    }
}
```

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/MbickstOkd9874RHK/builds/EgSBl8uLCvrf9t56r/openapi.json
