# CT Underground Storage Tanks - Facility & Enforcement Register (`j0401/ct-ust-tanks`) Actor

Connecticut's underground storage tank register (public open data): 49.4K tank records across 12,752 facilities, with the named responsible parties, inspection findings and violation text, and DEEP enforcement actions (field NOV, warning letter, red tag, consent order) linked on one facility id.

- **URL**: https://apify.com/j0401/ct-ust-tanks.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 ct ust facility 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

## CT Underground Storage Tanks - Facility & Enforcement Register

### Low cost

**From $0.00005 per record, down to $0.00003 at Gold** - pay per record delivered, and nothing for the query. Cost scales with what you pull, not with the size of the register.

Connecticut's underground storage tank register, from DEEP. Every tank in the state, who is responsible for it, what inspectors found, and what the state did about it - assembled from four linked tables and resolved on one facility id.

### What you get

**~49,900 tank records across 12,752 facilities**, each with tank status, substance, capacity, construction and leak-detection fittings. Four linked tables ride on the same facility id:

| Table | Rows | What it holds |
|---|---|---|
| tanks | ~49,900 | a tank or compartment: status, substance, capacity (gal), construction, piping, spill/overfill fittings, install date, GPS |
| contacts | ~51,700 | the named parties by role - Owner, Registrant, Operator, Class A/B Operator, Property Owner - each with their own address and phone |
| compliance | ~29,000 | inspection findings: the violation, its description, how it was resolved, and the red-tag / NOV flags |
| enforcement | ~8,900 | DEEP enforcement actions: field NOV, warning letter, red tag order, consent order, referral to the AG, with status and resolution |

### Modes

- **tanks** (default) - tank rows
- **contacts** - the responsible parties
- **compliance** - findings and violation text
- **enforcement** - state enforcement actions
- **facility** - everything above for one facility id, in one response
- **aggregate** - counts by status, substance, method, enforcement type, city

A `facility` query is the whole point: hand it a facility id and you get that site's tanks, its contacts, its inspection history and its enforcement record together.

### Example inputs

**One facility's whole record** - `mode=facility` returns the site's tanks, its
contacts, its inspection findings and its enforcement actions together.

```json
{ "mode": "facility", "facilityId": "64-51055", "maxResults": 200 }
```

**Gasoline tanks in Hartford**

```json
{ "mode": "tanks", "city": "HARTFORD", "substance": "Gasoline", "maxResults": 50 }
```

**DEEP actions still open**

```json
{ "mode": "enforcement", "enforcementStatus": "Active", "maxResults": 50 }
```

**Tank counts by status** - one row per `tankStatus`.

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

### Why this is hard

The four tables do not agree on anything. The enforcement table names the facility key `relatedfacilityid` while the other three call it `agencyfacilityid`, and ~690 enforcement rows carry no facility id at all. `tanklastuseddt` mixes real dates with one impossible value in 2086 - and ~12,900 tanks have no last-used date at all, because a tank still in use has never been emptied. Treating "no date" the same as "bad date" silently deletes a quarter of the register. Every one of those is handled here, and the row semantics (a facility is a *parent* - one site holds many tanks) are preserved rather than flattened away.

### Example output

**One tank** - `mode=tanks`, `facilityId=64-51055` returns the site's tanks,
including this one:

```json
{
  "platform": "ct-ust",
  "source": "ct-deep-tanks",
  "corpus": "tanks",
  "recordKind": "tank",
  "mode": "tanks",
  "groupKey": "",
  "groupCount": "",
  "groupBy": "",
  "facilityId": "64-51055",
  "facilityName": "ValCo Fuels",
  "facilityAddress": "84 AIRPORT RD",
  "facilityCity": "HARTFORD",
  "facilityZip": "06114",
  "tankId": "B",
  "tankStatus": "Currently In Use",
  "compartment": "b",
  "capacityGallons": "4000",
  "substance": "Gasoline",
  "tankLastUsed": "",
  "closureType": "",
  "tankConstruction": "Fiberglass Reinforced Plastic",
  "tankMods": "Double Walled (brine or pressure/vac monitor)",
  "pipeConstruction": "Rigid Fiberglass Reinforced Plastic",
  "pipeMods": "Containment Sumps @ Dispensers,Containment Sumps @ Tanks,Double Walled",
  "installationDate": "2026-08-01",
  "spillInstalled": "Spill Bucket",
  "overfillInstalled": "Audible Alarm",
  "latitude": "41.738312",
  "longitude": "-72.667543",
  "affiliationType": "",
  "contactLastName": "",
  "contactFirstName": "",
  "contactAddress1": "",
  "contactAddress2": "",
  "contactCity": "",
  "contactState": "",
  "contactZip": "",
  "contactPhone": "",
  "contactExt": "",
  "contactName": "",
  "inspectionDate": "",
  "complianceDate": "",
  "complianceMethod": "",
  "complianceNotes": "",
  "violationName": "",
  "violationDescription": "",
  "inFnovOrWarning": "",
  "inRedTag": "",
  "fieldNovId": "",
  "warningLetterId": "",
  "facilityState": "",
  "enforcementId": "",
  "enforcementType": "",
  "enforcementStatus": "",
  "actionStart": "",
  "actionEnd": "",
  "resolution": "",
  "violationCount": "",
  "clientType": "",
  "clientLastName": "",
  "clientFirstName": "",
  "clientAddress": "",
  "clientCity": "",
  "clientState": "",
  "clientZip": "",
  "sourceUpdatedAt": "2026-09-22"
}
```

### Output

Every record carries the same key set regardless of which table it came from, so a cross-table read is one schema. Dates arrive as `YYYY-MM-DD`.

### Notes

- Public open data from data.ct.gov. No login, no scraping, no rate-limit games.
- Connecticut DEEP publishes this data for exactly this purpose - the register is how the state tells the public which tanks are where.
- Charges are metered per record delivered, so a targeted query costs a fraction of a cent.

# Actor input Schema

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

tanks = tank rows (default). contacts = the named parties on a facility (Owner / Operator / Class A Operator). compliance = inspection findings and violation text. enforcement = DEEP actions (field NOV, warning letter, red tag, consent order). facility = EVERYTHING for one facility id, in one response. aggregate = one count row per group.

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

Which table to aggregate - only used when mode=aggregate. Ignored otherwise.

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

Aggregate dimension, only used when mode=aggregate. Valid per table: tanks -> tankStatus, substance, closureType, city; contacts -> affiliation, city, state; compliance -> complianceMethod, violation, city, tankId; enforcement -> enforcementType, enforcementStatus, clientType, city. Blank = a sensible default per table. Blank = a sensible default for the selected table - do not leave it set when switching corpus.

## `facilityId` (type: `string`):

Exact facility id, e.g. '135-9468'. One id links the tanks, contacts, compliance and enforcement tables - this is the through-key. Blank = any. Use it with mode=facility to pull one site's whole record.

## `name` (type: `string`):

Facility name substring, e.g. 'SHELL'. Blank = any. Not available on the compliance table (which carries no name).

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

City substring, e.g. 'HARTFORD'. Blank = any.

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

ZIP prefix, e.g. '06103' or '061'. Blank = any.

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

Exact tank status. Blank = any.

## `substance` (type: `string`):

Exact substance the tank holds. Blank = any.

## `closureType` (type: `string`):

Exact closure method (tanks table only). Blank = any.

## `affiliation` (type: `string`):

Exact party role (contacts table only). Blank = any.

## `complianceMethod` (type: `string`):

How the finding was resolved (compliance table only). Blank = any.

## `violation` (type: `string`):

Violation-name substring (compliance table only), e.g. 'RELEASE DETECTION'. Blank = any.

## `enforcementType` (type: `string`):

Exact DEEP action (enforcement table only). Blank = any.

## `enforcementStatus` (type: `string`):

Exact action status (enforcement table only). Blank = any.

## `installedFrom` (type: `string`):

Earliest tank installation date, YYYY-MM-DD (tanks table only). Blank = any.

## `installedTo` (type: `string`):

Latest tank installation date, YYYY-MM-DD (tanks table only). Blank = any.

## `inspectedFrom` (type: `string`):

Earliest inspection date, YYYY-MM-DD (compliance table only). Blank = any.

## `inspectedTo` (type: `string`):

Latest inspection date, YYYY-MM-DD (compliance table only). Blank = any.

## `actionFrom` (type: `string`):

Earliest enforcement action start date, YYYY-MM-DD (enforcement table only). Blank = any.

## `actionTo` (type: `string`):

Latest enforcement action start date, YYYY-MM-DD (enforcement table only). Blank = any.

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

Cap records returned for this run. Default 50 keeps the daily auto-test fast. 0 = no cap (the full matching slice, up to 10,000).

## Actor input object example

```json
{
  "mode": "tanks",
  "corpus": "tanks",
  "groupBy": "",
  "facilityId": "",
  "name": "",
  "city": "",
  "zip": "",
  "tankStatus": "",
  "substance": "",
  "closureType": "",
  "affiliation": "",
  "complianceMethod": "",
  "violation": "",
  "enforcementType": "",
  "enforcementStatus": "",
  "installedFrom": "",
  "installedTo": "",
  "inspectedFrom": "",
  "inspectedTo": "",
  "actionFrom": "",
  "actionTo": "",
  "maxResults": 50
}
```

# Actor output Schema

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

Connecticut UST 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/ct-ust-tanks").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/ct-ust-tanks").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/ct-ust-tanks --silent --output-dataset

```

## MCP server setup

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

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/Lw6fhofEbc44jKU6F/builds/4guG4spDB9apUudRJ/openapi.json
