TX TCEQ Petroleum Tank Leak (LPST) Site Delta
Pricing
$10.00 / 1,000 lpst case status change delivereds
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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
6 days ago
Last modified
Categories
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:
changeType | What happened at TCEQ |
|---|---|
new_case | A new leak case appeared in your area — a petroleum release was reported at a site inside your perimeter. |
case_closed | TCEQ closed an existing case (closure_date went from empty to a date). |
case_reopened | A previously closed case was reopened (closure_date went back to empty). |
case_updated | TCEQ 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
monitorIdnames a durable history. The first run of a newmonitorIdis a silent baseline — it records the current state of every case in each area, delivers nothing and charges nothing.- 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). - 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. - An optional HTTPS
webhookUrlreceives 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 itsrun_summaryrow) — but a default-dataset row does not by itself prove the row was billed: the row is written beforeActor.charge()runs, so if charging then fails or comes backcharge_unknown, the row is present but not confirmably paid.run_summary.eventsBilledand 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:
countyandcityare 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 ANTONIOandSAN ANTONIO HEIGHTSare different cities and always will be. If a county name is misspelled, the area simply contains zero cases (reported honestly incoverage, not as an error). - An area holding more than 12 000 LPST cases is refused, not silently truncated (
target_too_broadincoverage). 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 anlpst_idrepeats or goes backwards, the whole watch is reported assource_access_limitedfor 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 returnscount(reported_date)andcount(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_caseand 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), andcoveragelists the identifiers of the cases that went missing in this particular run (newlyMissingIds, per-watchnewlyMissing) 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_dateandclosure_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, inlpst_idorder. - Only one run per
monitorIdat a time. Concurrent runs are serialised by a durable lease; a run that loses the lease stops immediately, reportslease_lost, and never delivers or charges anything further. - A
watchIdis permanently bound to its area on first use. Reusing the samewatchIdwith a different county/city/ZIP fails the run withwatch_config_mismatchbefore 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.