TX TCEQ Petroleum Tank Leak (LPST) Site Delta avatar

TX TCEQ Petroleum Tank Leak (LPST) Site Delta

Pricing

$10.00 / 1,000 lpst case status change delivereds

Go to Apify Store
TX TCEQ Petroleum Tank Leak (LPST) Site Delta

TX TCEQ Petroleum Tank Leak (LPST) Site Delta

Watches Texas TCEQ Leaking Petroleum Storage Tank (LPST) sites by county, city or ZIP and charges only for a real change: a new leak case reported nearby, a case closed by TCEQ, or a case reopened. For Phase I ESA and environmental consultants, lenders, real estate due diligence and insurers.

Pricing

$10.00 / 1,000 lpst case status change delivereds

Rating

0.0

(0)

Developer

Radu Furtuna

Radu Furtuna

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

6 days ago

Last modified

Share

Watches the official Texas Commission on Environmental Quality (TCEQ) registry Leaking Petroleum Storage Tank (LPST) Sites on data.texas.gov for the Texas counties, cities or ZIP codes you name, and reports only what actually changed since your last run:

changeTypeWhat happened at TCEQ
new_caseA new leak case appeared in your area — a petroleum release was reported at a site inside your perimeter.
case_closedTCEQ closed an existing case (closure_date went from empty to a date).
case_reopenedA previously closed case was reopened (closure_date went back to empty).
case_updatedTCEQ corrected a case date (reported or closure date changed to a different date).

Nothing else is delivered and nothing else is charged. A run where nothing changed produces no paid events at all.

Who this is for

  • Phase I ESA / environmental consultants — standing watch on the parcels and corridors you screen, instead of re-running a one-off registry lookup every quarter.
  • Lenders and CRE underwriters taking gas stations, truck stops, fleet yards or former fuel sites as collateral: a new LPST case (or a closure) inside the collateral's ZIP changes the file.
  • Real estate developers and title/insurance underwriters watching a target area before or during a transaction.
  • Environmental counsel tracking when TCEQ finally closes a client's open case.

How it works

  1. monitorId names a durable history. The first run of a new monitorId is a silent baseline — it records the current state of every case in each area, delivers nothing and charges nothing.
  2. Every later run takes a fresh snapshot of each area and compares it against the durable per-case index (lpst_id -> reported date, closure date, content hash, change sequence).
  3. Each genuine change is written to the dataset, then charged at most once — the right to deliver and charge is granted by a single atomic claim keyed on lpst_id + change sequence + content hash (see Delivery guarantee below). A case that is closed, later reopened, and then closed again on the same date produces three separate paid events, not two: the change sequence distinguishes repeats across the case's history.
  4. An optional HTTPS webhookUrl receives a digest of the delivered (paid) changes.

Run it on an Apify schedule (daily is a good fit — TCEQ refreshes this dataset daily).

Pricing

Pay per event: one event per delivered case change. No change, no charge. Charging happens only after the row has been written to both the durable dataset and the run's own output dataset.

Delivery guarantee: at-most-once (not exactly-once)

For any one computed change, the row is delivered to the dataset and the event is charged no more than once. The right to do either is handed out by the only atomic primitive Apify offers — RequestQueue.addRequest(uniqueKey) — in a separate, named request queue (<storage prefix>-<monitorId>-claims) that is used purely as an append-only journal of granted rights: its requests are never handled and never deleted. Apify's key-value store has no compare-and-swap, conditional write or ETag, so it cannot be the source of that right; it only holds a diagnostic state machine for reporting.

The trade-off is deliberate: if a run crashes after taking the claim, that change can be lost (it is closed as dataset_unknown / charge_unknown and never re-delivered) — but you are never charged twice for it. Two boundaries of the guarantee, stated honestly:

  • it holds for as long as the named claims queue exists. Anyone with account access can delete or recreate that queue through the Console/API, which starts the journal from scratch — an unavoidable limit of any durable storage, not a defect of the protocol;
  • it applies from the build in which the claim gate shipped onward. Older builds must not keep running against the same monitorId.

The same gate also covers the "case disappeared from the area" / "case came back" notices: no row and no charge, but the notice leaves the actor in coverage, the digest and the webhook, and a sent notification cannot be recalled. Its claim key contains no run id — it is pinned to a durable disappearance epoch — so the same disappearance is announced once, not once per run.

Honest limits

  • The durable dataset is a delivery-attempt log, not a guaranteed mirror of the default dataset. Each row is written to the durable dataset first, then mirrored to the run's default dataset before billing proceeds for that row. If the durable write succeeds but the default-dataset mirror write fails (e.g. transient Apify storage error), the item is marked dataset_unknown, billing for it is permanently blocked (fail-closed — we never charge for a row we can't confirm was delivered), and the run is not retried into re-creating that exact row. The durable dataset can therefore end up with a small number of orphan rows that were never mirrored and never billed. The default dataset is the canonical log of rows successfully written to this run's output (see its run_summary row) — but a default-dataset row does not by itself prove the row was billed: the row is written before Actor.charge() runs, so if charging then fails or comes back charge_unknown, the row is present but not confirmably paid. run_summary.eventsBilled and Apify's own billing ledger are the source of truth for confirmed payment, not the presence of a row in either dataset.
  • This is a registry-publication monitor, not an environmental assessment. It reports what TCEQ publishes and when that publication changes. It is not a Phase I/II ESA, not a contamination determination, not a legal opinion and not a clean-site certification. A closed case means TCEQ closed its administrative case, nothing more. Before any transaction, loan or policy decision, use a licensed environmental professional and TCEQ's own records.
  • County/city/ZIP values must match how TCEQ writes them. Verified live over the whole dataset on 13.09.2026: county and city are stored upper-case with no surrounding whitespace, so your input is upper-cased and matched exactly — no fuzzy matching, no substring search, no merging of similar place names. SAN ANTONIO and SAN ANTONIO HEIGHTS are different cities and always will be. If a county name is misspelled, the area simply contains zero cases (reported honestly in coverage, not as an error).
  • An area holding more than 12 000 LPST cases is refused, not silently truncated (target_too_broad in coverage). No Texas county comes close today — the largest, HARRIS, holds 4 461 cases (verified live) — but a truncated snapshot would fabricate both "new" and "vanished" cases, so it is refused instead.
  • Snapshot completeness is verified against the source's own count on every run. The actor first asks TCEQ how many rows match the area, then pages through them with keyset pagination on the unique lpst_id (no $offset, which can skip or repeat rows if the data changes mid-run). If the collected row count does not match the count TCEQ itself reported, or an lpst_id repeats or goes backwards, the whole watch is reported as source_access_limited for that run — no partial baseline, no partial delta, no billing on a snapshot whose completeness cannot be trusted. The next run recovers on its own. The same count query also returns count(reported_date) and count(closure_date), and the number of non-empty dates actually collected must match them exactly — so a date column that disappears from the feed, for all rows or for only some of them, is caught instead of being delivered as a wave of "case reopened" events.
  • Count + keyset pagination is a completeness check, not snapshot isolation — and we do not pretend otherwise. The Socrata API offers no transactional snapshot: the count query and the pages are separate reads of a live dataset. The checks above catch a row count that changes mid-run, a skipped page, a repeated or out-of-order key. They cannot catch a swap — one row deleted and another inserted between two reads, leaving the total unchanged. In that (rare, TCEQ-side) case the run sees the swapped-in case as new_case and the swapped-out one as missing; nothing is silently mis-classified as a status change of an existing case, but the run is not an atomic snapshot and is not advertised as one. TCEQ publishes this dataset in a daily batch, so mid-run mutation is unlikely rather than impossible.
  • A case that disappears from your area is reported, never billed, and never deleted from the index. TCEQ does not delete LPST cases, so this normally means a case was re-assigned to a different county/city. The record is kept frozen so that, if the case comes back, it cannot be charged again as if it were brand new. Each disappearance also gets a durable tombstone (firstMissingAt, lastSeenAt, missingRuns), and coverage lists the identifiers of the cases that went missing in this particular run (newlyMissingIds, per-watch newlyMissing) and the ones that came back (returnedFromMissing) — not just an ever-growing total. A case that returns unchanged costs nothing; a case that returns closed (or reopened) is charged once, as the real status change it is.
  • Watched areas must not overlap. Billing and de-duplication are scoped per watchId, so a case sitting inside two overlapping areas would be delivered — and charged — twice for one change. Rather than let that happen quietly, overlapping (or possibly overlapping) area combinations inside one county are rejected before the run starts: watch the whole county with one watch, or watch several distinct cities/ZIPs with one watch each, but not both at once. Different counties never overlap.
  • A run in which some areas failed never reports itself as a clean "nothing changed". If at least one watch could not be checked, the run's reason becomes partial_watch_failures, the digest says in plain text how many areas were not checked, and the "no changes found" wording is explicitly qualified as covering only the checked part of your perimeter.
  • Only two fields drive billing: reported_date and closure_date. Renaming a site, correcting its street address or updating the tank registration number is administrative noise and is deliberately not charged, even though the row's other fields will show the newest values whenever a real change is delivered.
  • At most 200 case changes per area per run are delivered (change_cap_reached). This protects your wallet from an anomalous bulk re-publication on the source side. The remaining changes are not lost — they are picked up by the following runs, in lpst_id order.
  • Only one run per monitorId at a time. Concurrent runs are serialised by a durable lease; a run that loses the lease stops immediately, reports lease_lost, and never delivers or charges anything further.
  • A watchId is permanently bound to its area on first use. Reusing the same watchId with a different county/city/ZIP fails the run with watch_config_mismatch before any network request — otherwise one area's accumulated history would silently be presented as another's.
  • Update cadence is TCEQ's, not ours. The dataset refreshes daily; running more often than daily just produces empty runs.

Output

Dataset rows carry watchId, lpstId, changeType, caseStatus/previousCaseStatus, closureDate/previousClosureDate, reportedDate/previousReportedDate, siteName, siteAddress, siteAddressDescription, city, county, zip, tceqRegion, refNum (the TCEQ Regulated Entity number), pstRegistration, plus monitorId, runId, discoveredAt and eventId.

coverage (key-value store) records, for every run: each watch's area, status, reason, rows in its snapshot, changes detected, cases missing from the snapshot and the ones that newly went missing or came back, plus run-level counters — recordsDelivered, recordsBilled, sourceAccessLimitedCount, targetTooBroadCount, changeCapReachedCount, missingFromSnapshotCount, newlyMissingCount and newlyMissingIds (watchId:lpstId, capped at 100 per run with newlyMissingIdsTruncated).

Source

Texas Open Data Portal, data.texas.gov, dataset hedz-nn4q, published by the Texas Commission on Environmental Quality. Public, free, no key required. 29 842 cases and 1 074 open cases as of 13.09.2026.