# NY Environmental Records - Spills, Sites & Tanks (`j0401/ny-environmental-records`) Actor

New York (NYSDEC) environmental data in one API: 574,559 spill incidents, 237,135 remediation sites (Superfund, Brownfield, VCP, ERP, RCRA) and 286,691 bulk storage tanks. Filter by county, ZIP, city, DEC region, facility, program, material or status; aggregate by county, program or year.

- **URL**: https://apify.com/j0401/ny-environmental-records.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 ny environmental 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

## New York Environmental Records

Three New York State (NYSDEC) environmental datasets in one API and one record shape: **spill incidents**, **remediation sites** and **bulk storage tanks**.

### Low cost

Environmental records: **from $0.00005 per record, down to $0.00003 at Gold.**

### What you get

Every row carries `corpus` and `recordKind` so a mixed export is never ambiguous: `spill`, `remediation-site` or `storage-tank`.

**Spills** - 574,581 rows

- `programNumber` (spill number), `spillDate`, `receivedDate`, `closeDate`
- `facilityName`, `address`, `address2`, `city`, `county`, `zip`, `region`
- `materialName`, `materialFamily`, `quantity`, `units`, `recovered`
- `spillSource`, `contributingFactor`, `waterbody`
- `status` / `statusLabel` - `open` (no close date) or `closed`

**Remediation sites** - 237,230 rows

- `programNumber`, `programType` - `HW` (State Superfund), `BCP` (Brownfield), `VCP`, `ERP`, `RCRA`, plus `SP` (spill sites, 491 rows) and `DWC` (61)
- `siteClass` / `siteClassLabel` - 02 / 03 / 04 registry classes, `A` active, `C` completed, `P` preliminary, `PR` RCRA, `N` no further action. (`05` is a valid registry class but has **no rows in this extract**.)
- `facilityName`, `address`, `city`, `county`, `zip`, `region`, `latitude`, `longitude`
- `contaminants`, `wasteName`, `controlCode`, `controlType`, `operableUnit`
- `swisCode`, `projectName`
- `ownerName`, `ownerAddress`, `ownerCity`, `ownerState`, `ownerZip`
- `operatorName`, `disposalName`, `disposalAddress`
- `projectCompletionDate`

**Bulk storage tanks** - 250,803 rows (the source's 286,691 rows include 35,890 empty placeholders with no tank number, status or capacity; those are never delivered or billed)

- `programNumber`, `programType` - `PBS` (petroleum) and `CBS` (chemical) are delivered. `MOSF` (major oil, 12,208 source rows) and `LNG` carry no tank number, status or capacity in this extract (they are facility-level registrations, mostly vessels), so **no MOSF or LNG row is delivered or billed**.
- `tankNumber`, `status` / `statusLabel`, `tankType`, `tankLocation`, `capacityGallons`
- `installDate`, `closeDate`, `expirationDate`
- `materialName`, `percentOfMaterial`
- `siteType`, `siteStatus`
- `latitude`, `longitude` (from the source point) and the original `utmX`, `utmY`

### Examples

The 50 most recent spill reports:

```json
{ "corpus": "spills", "maxResults": 50 }
```

Spills in one county:

```json
{ "county": "Erie", "maxResults": 100 }
```

Spills at an address (the filter matches from the **start** of the address string, so include the house number when the source has one):

```json
{ "address": "100 MAIN", "maxResults": 100 }
```

A site by its street address:

```json
{ "corpus": "sites", "address": "505 WINDING", "maxResults": 50 }
```

Gasoline spills in one county:

```json
{ "county": "Erie", "material": "Gasoline", "maxResults": 100 }
```

Open spill reports only:

```json
{ "openOnly": true, "maxResults": 100 }
```

Remediation sites in a county:

```json
{ "corpus": "sites", "county": "Nassau", "maxResults": 100 }
```

The most serious site class (registry class 2 - a significant threat):

```json
{ "corpus": "sites", "siteClass": "02", "maxResults": 100 }
```

Tanks that are still in service anywhere in a county:

```json
{ "corpus": "tanks", "county": "Erie", "tankStatus": "In Service", "maxResults": 100 }
```

Every storage tank inside a latitude/longitude box around New York City:

```json
{
  "corpus": "tanks",
  "north": 40.95, "south": 40.55, "east": -73.70, "west": -74.05,
  "maxResults": 200
}
```

Remediation sites in the same box:

```json
{
  "corpus": "sites",
  "north": 41.00, "south": 40.40, "east": -73.50, "west": -74.50,
  "maxResults": 200
}
```

Spills by county:

```json
{ "mode": "aggregate", "groupBy": "county" }
```

Sites by program:

```json
{ "corpus": "sites", "mode": "aggregate", "groupBy": "programType" }
```

Tank capacity by status:

```json
{ "corpus": "tanks", "mode": "aggregate", "groupBy": "tankStatus", "metric": "capacityGallons" }
```

### Coverage and honest limits

- Counts measured **2026-10-28** (source last updated 2026-10-28): **574,581 spill reports / 237,230 remediation-site records / 250,803 storage tanks = 1,062,614 delivered records**.
- **One row is not one place.** A spill row is a spill report. A remediation-site row is a site  operable unit  institutional/engineering-control record, so a large site legitimately appears several times (about 4% of delivered site rows are verbatim duplicates of another row - the source publishes them twice, and each is delivered and metered as a source record). A tank row is a single tank, so one facility appears once per tank. Tank rows with no tank number, no status and no capacity are dropped (35,890 source placeholders).
- 🔴 **ZIP is published on only about 10% of spill rows** (57,862 of 574,581). Spill ZIP filtering returns few rows by design; county is complete on every row. A 5-digit ZIP query also matches ZIP+4 values (`12903-2116`).
- 🔴 **New York City is published by county name, not borough name**: Manhattan is `New York`, Brooklyn is `Kings`, Staten Island is `Richmond`; `Bronx` and `Queens` are spelled as expected. Filtering `county="New York"` is Manhattan only.
- The spill corpus publishes **87 distinct `county` values**, because the state records where a report came from - including spills outside New York (`Canada - Region 5`, `Connecticut - Region 1`, `New Jersey - Region 2`, `Pennsylvania - Region 3`, `Vermont - Region 4`, ...) and reservation names. They are not separate NY counties and are returned as published.
- 🔴 **Tank `percentOfMaterial` is raw source data, not a percentage.** The state stores it scaled (a full tank reads `10000.00`) and it is populated across the programmes, not just chemical bulk storage: every delivered tank carries it (PBS 226,215 read `10000.00`, CBS 2,006). It is passed through unchanged and is never presented as a percentage.
- **Tank coordinates** come from the source's published point, with the original UTM easting/northing kept alongside. Spill records carry no coordinates at all - the source publishes an address, not a point, so `north` / `south` / `east` / `west` are rejected for `corpus=spills` rather than silently ignored.
- **Bounding-box search** works on `sites` and `tanks`: give all four of `north`, `south`, `east`, `west` (longitudes negative in New York) and the predicate runs against the source's point column. A partial box, or one with the edges swapped, is rejected with an error rather than approximated. About **10% of delivered tank rows carry no published point** (26,285 rows) and therefore never fall inside any box - use the address, city or county filters for those.
- **`projectCompletionDate` is the program's project completion date and may be a planned future date.** It is not proof that remediation finished.
- `spillDate` is blank on 151 spill rows and `receivedDate` on 503; the row is still returned with blank dates rather than dropped. `installDate` is blank on 50,440 delivered tanks.
- `ownerName` / `operatorName` on remediation sites are the **published site owner and operator of record** (public record), not the current owner of the land and not a mailing list.
- `sourceUpdatedAt` is the state's last dataset refresh date for the chosen corpus.

### Related

Also available in this account: **NY Property Assessments** (tax-lot assessment and sale records) and **NYC HPD Violations** (housing-code violations by building).

# Actor input Schema

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

spills = spill incidents (default). sites = environmental remediation sites. tanks = bulk storage tanks.

## `address` (type: `string`):

Street-address prefix, case-insensitive (e.g. "MAIN ST"). Works on all three corpora; spill records have no coordinates, so there is no radius search.

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

Exact county name, case-insensitive, e.g. "Erie" or "Nassau".

## `zip` (type: `string`):

ZIP prefix, e.g. "14201" (a 5-digit query also matches ZIP+4 values such as 14201-1234). The spill corpus publishes a ZIP on only about 10% of rows.

## `city` (type: `string`):

City-name prefix, case-insensitive, e.g. "BUFFALO".

## `region` (type: `number`):

NYSDEC region number, 0-9. Region 0 is reserved for vessels with no fixed location; those rows are facility-level tank registrations with no tank number or status and are not delivered.

## `facilityName` (type: `string`):

Facility-name prefix, case-insensitive, e.g. "BETHPAGE".

## `programNumber` (type: `string`):

Exact program id: spill number (spills) or program number (sites / tanks), e.g. "130003A" or "2-481637".

## `programType` (type: `string`):

Sites: HW (State Superfund), BCP, VCP, ERP, RCRA. Tanks: PBS, CBS, MOSF, LNG. Not used by the spill corpus.

## `material` (type: `string`):

Material-name prefix. Spills and tanks match the stored material name; sites match the published contaminants. E.g. "Gasoline".

## `units` (type: `string`):

Exact spilled-quantity unit, e.g. "Gallons" or "Pounds". Required when metric=quantity, because the corpus publishes both and they cannot be added together.

## `siteClass` (type: `string`):

Site classification / status code: 02, 03, 04, 05, A (active), C (completed), P (preliminary), PR (RCRA), N (no further action).

## `tankStatus` (type: `string`):

Exact tank status, e.g. "In Service", "Closed - Removed", "Closed - In Place", "Out of Service".

## `north` (type: `number`):

Sites and tanks only: north edge of a latitude/longitude box. Give all four of north, south, east, west. Spill records carry no coordinates.

## `south` (type: `number`):

South edge of the box; must be less than north.

## `east` (type: `number`):

East edge of the box, e.g. -73.5 for western Long Island; must be greater than west.

## `west` (type: `number`):

West edge of the box, e.g. -74.5; longitudes are negative in New York.

## `openOnly` (type: `boolean`):

When true, only spill records with no close date (spills corpus only).

## `dateFrom` (type: `string`):

ISO date YYYY-MM-DD. Applies to the corpus's primary date: spill date (spills), project completion date (sites), install date (tanks).

## `dateTo` (type: `string`):

ISO date YYYY-MM-DD, inclusive, on the same primary date column as dateFrom.

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

rows = records (default). aggregate = one row per group.

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

Dimension for mode=aggregate (blank = county). Only the dimensions that exist for the chosen corpus are accepted.

## `metric` (type: `string`):

What to aggregate (blank = count). quantity sums the spilled quantity and REQUIRES a units filter (Gallons or Pounds - they cannot be mixed); capacityGallons sums tank capacity (tanks only).

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

Cap on records pushed in rows mode (0 = default 50; up to 5,000 per run). Each record is metered individually, so there is no per-run charge cap.

## Actor input object example

```json
{
  "corpus": "",
  "openOnly": false,
  "mode": "",
  "groupBy": "",
  "metric": "",
  "maxResults": 50
}
```

# Actor output Schema

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

New York environmental 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/ny-environmental-records").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/ny-environmental-records").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/ny-environmental-records --silent --output-dataset

```

## MCP server setup

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

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/btuuvRnW32xjFo6I1/builds/0NHPlhaDjEEc1pqi9/openapi.json
