# Depth to Groundwater Screener - USGS Water Levels, Phase I ESA (`malonestar/usgs-groundwater-depth-screener`) Actor

Depth to groundwater near any US coordinate from USGS monitoring wells. Returns depth below land surface in feet, the measurement date, its age, datum and approval status, plus shallowest and median depth in radius. Stale, dry, pumping and artesian readings are labelled, never passed off as current.

- **URL**: https://apify.com/malonestar/usgs-groundwater-depth-screener.md
- **Developed by:** [Kyle Maloney](https://apify.com/malonestar) (community)
- **Categories:** Real estate, Developer tools, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.40 / 1,000 groundwater results

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Depth to Groundwater Screener — USGS Water Levels for Phase I ESA, Geotech & Dewatering

**How deep is the water table at this address, when was it actually measured, and how much has it moved?**

This actor answers that for any US coordinate from the **USGS Water Data OGC API** — the
authoritative national record of groundwater levels. For each site you get the depth to
water in **feet below land surface**, the **date that measurement was taken**, **how old
it is**, its vertical datum and approval status, the **shallowest** and **median** depth
across every well in the radius, and — optionally — the **full observed fluctuation
range** from a well's complete history.

It is built around one uncomfortable fact about US groundwater data: **most of it is
old.** A great many USGS wells were last measured in the 1960s, 1980s or 1990s. A
screener that prints "depth to groundwater: 9.7 ft" without saying that the reading is
from **April 1962** is handing you a confident wrong answer. This one puts the date, the
age in years, and an explicit staleness flag on every single row, and it will tell you
plainly when *every* reading near your site is archival.

### Who this is for

- **Phase I ESA consultants** — the ASTM E1527-21 §8.2.2 *physical setting* leg. Depth to
  groundwater and the inferred flow setting are part of every report, and this is the
  fastest defensible way to source it with a citation and a date.
- **Geotechnical and civil engineers** — seasonal high water table for foundation,
  basement, slab and pavement design.
- **Dewatering and excavation contractors** — turn on `includeHistory` and you get the
  observed min, max and total fluctuation over a well's whole record, which is what
  actually sizes a dewatering system. A single latest reading cannot give you that.
- **Environmental engineers** — vadose-zone thickness, contaminant-migration screening,
  monitoring-well network design.
- **Land and site-selection teams** — septic feasibility, stormwater infiltration,
  basement viability, and flagging artesian conditions before you buy.

### What makes the answer trustworthy

Every actor in this portfolio is built to a simple rule: **a crash is cheap, a confident
wrong answer is expensive.** Concretely, here:

- **Only an ambient *static* water level is reported as depth to groundwater.** USGS marks
  each reading with condition qualifiers. A **pumping** measurement is a drawn-down level,
  not the water table. A **dry** well means the water is *deeper than the hole* and USGS
  publishes no value at all — read carelessly, that null becomes "water at 0 ft", i.e.
  water at the surface, which is the exact opposite of the truth. Both are excluded from
  every depth statistic and reported separately.
- **Artesian levels are preserved as negative numbers.** A well whose water stands 81 ft
  *above* land surface reports `-81.16`, never `81.16` and never dropped.
- **A water-level *elevation* is never silently turned into a depth.** USGS publishes some
  levels as elevation above NAVD88, NGVD29 or local mean sea level. A depth is only
  derived when the well's own land-surface datum matches the measurement datum exactly —
  the NGVD29-to-NAVD88 offset is location-specific and is never assumed. When it can't be
  derived, you get `null` plus the reason, never a guess.
- **`null` means "not checked". `false`/`0` means "checked and negative".** If USGS is
  unreachable the row says `source_unavailable` and carries no verdict at all. It will
  never tell you a site has deep groundwater because a request failed.
- **Completeness is proven, not assumed.** This API publishes **no result total** of any
  kind, so there is no server count to check against. The actor pages the cursor to
  exhaustion and proves termination from three independent signals that must agree — the
  final page carried no next link, the final page was short, and the unique feature IDs
  equal the sum of every page's count. It publishes that proof on the row in
  `paging_exhaustion_proof`, and if it cannot prove it, `results_truncated` is `true`.
- **The live source is verified before any billable row.** Every run checks that the four
  required collections still exist, that parameter code 72019 still means *depth below
  land surface in feet*, that two pinned canary wells return their pinned identity and
  earliest measurement (one with a record back to **1928**), that a mid-Pacific control
  returns zero, and that a cursor walk really terminates. If any of that fails the run
  fails, emits nothing, and bills nothing.

### Example input

```json
{
  "assets": [
    { "lat": 40.7891, "lon": -73.1350, "label": "Long Island NY industrial parcel", "radiusMiles": 1 },
    { "lat": 39.7392, "lon": -104.9903, "label": "Denver CO redevelopment site", "radiusMiles": 1 },
    { "lat": 29.7604, "lon": -95.3698, "label": "Houston TX warehouse", "radiusMiles": 1 }
  ],
  "radiusMiles": 1,
  "includeWellDetail": true,
  "maxWellsPerAsset": 8,
  "includeHistory": true,
  "historyWellCount": 2,
  "maxAgeYearsForCurrent": 5,
  "shallowGroundwaterThresholdFeet": 15
}
```

`assets` is the only required field. Everything else has a sensible fallback applied in
code (documented per field below) — deliberately **not** a schema default, because an
Apify input-schema `default` is injected server-side into every run and would silently
narrow queries you never asked to narrow.

### What a result looks like

The three example sites above return three genuinely different answers, and the
difference is the product:

| Site | Depth to water | Measured | Age | `data_currency_class` | `all_readings_stale` |
|---|---|---|---|---|---|
| Long Island NY | shallow, coastal-plain water table | 2026 | weeks | `current` | `false` |
| Denver CO | ~9.7 ft at the nearest well | **April 1962** | ~64 yr | `archival` | `true` |
| Houston TX | Gulf Coast aquifer | 2011 | ~15 yr | `historical` | `true` |

All three are *correct*. Only one of them is a current condition, and the row tells you
which.

### Output fields

Two row types share one schema. `record_type` is `asset_summary` (one per site) or
`well` (one per USGS monitoring location inside the radius, when `includeWellDetail`
is on).

#### Site summary — the answer

| Field | Meaning |
|---|---|
| `screening_result` | `static_depth_available`, `readings_present_no_static_depth`, `no_water_level_data_in_radius`, `no_monitoring_locations_in_radius`, or `source_unavailable`. The last one is not a negative finding. |
| `nearest_static_depth_to_water_feet` | The headline depth, in feet below land surface, at the nearest well with a usable static reading. |
| `nearest_static_reading_date` / `_age_years` | When that reading was taken, and how old it is. Always read these with the depth. |
| `nearest_static_reading_approval` | `Approved` or `Provisional`. |
| `nearest_static_well_id` / `_name` / `_distance_feet` / `_bearing_compass` | Which well it came from and where it is relative to your point. |
| `nearest_static_well_depth_feet` / `_aquifer_code` | Which aquifer that level represents. |
| `shallowest_depth_to_water_feet` | Shallowest static depth anywhere in the radius — usually the conservative number for excavation and basement work. |
| `depth_to_water_min_feet` / `_max_feet` / `_median_feet` / `depth_readings_used` | Distribution across the radius. A wide spread normally means multiple aquifers, not error. |
| `most_recent_reading_date_in_radius` / `_age_years` / `_well_id` | The honest answer to "how current is groundwater data here". |
| `data_currency_class` | `current` (<1 yr), `recent` (1–5), `historical` (5–20), `archival` (20+). |
| `all_readings_stale` / `readings_within_max_age_count` | Whether every reading near the site predates your `maxAgeYearsForCurrent` threshold. |
| `shallow_groundwater_flag` | Shallowest measured depth at or above your threshold. `null` — never `false` — when no usable depth was found. |
| `artesian_conditions_present` | A measured water level standing above land surface somewhere in the radius. |
| `phase1_physical_setting_flag` | Gated: `true` when shallow or artesian groundwater was measured, `null` whenever the data was not adequate to decide. |
| `depth_data_adequate_for_verdict` | Both sources answered **and** a usable static depth exists. Every flag above is gated on this. |
| `monitoring_locations_in_radius` / `groundwater_wells_in_radius` / `locations_with_water_level_data` / `wells_with_static_depth_reading` | The funnel from "sites nearby" to "sites that actually answer the question". |
| `wells_with_elevation_only_reading` / `wells_with_depth_derived_from_elevation` | Wells that publish an elevation rather than a depth, and how many allowed a datum-safe derivation. |
| `dry_wells_observed` / `pumping_only_wells_observed` / `artesian_wells_observed` / `static_readings_provisional_count` | Condition breakdown, so an excluded reading is visible rather than missing. |
| `measurements_source_status` / `locations_source_status` / `history_source_status` (+ `_error`) | Per-source outcome on every row. |
| `query_complete` / `results_truncated` / `paging_*` / `server_total_available` | The completeness proof described above. `server_total_available` is always `false` and says so deliberately. |
| `usgs_api_root` / `usgs_api_version` / `collections_verified` / `parameter_codes_queried` / `drift_checks_ran` / `checked_at` | Provenance. |
| `api_rate_limit_remaining` / `api_key_supplied` | Your remaining USGS quota for the hour. USGS only sends the quota header on an HTTP 429, so on a healthy run `api_rate_limit_remaining` is legitimately `null` — "not reported", never "zero left". |
| `asset_label` / `asset_lat` / `asset_lon` / `search_radius_miles` / `error` | Echo of your input, plus any per-site failure. |

#### Per-well rows — the evidence

| Field | Meaning |
|---|---|
| `well_rank_by_distance` | 1 = nearest. |
| `well_id` / `well_agency_code` / `well_site_number` / `well_name` | USGS identity. Names are often survey-grid codes, not plain language. |
| `well_site_type_code` / `well_site_type` | `GW` (well), `GW-TH` (test hole), `GW-MW`, `GW-EX`, `GW-HZ`, `GW-CR`, `GW-IW`, or a non-groundwater type. |
| `well_state` / `well_county` / `well_hydrologic_unit_code` | Administrative and watershed context. |
| `well_lat` / `well_lon` / `well_distance_feet` / `well_distance_miles` / `well_bearing_degrees` / `well_bearing_compass` | True haversine distance and bearing — not the bounding box that fetched the candidate. |
| `well_horizontal_accuracy` / `well_horizontal_method` | How precisely USGS knows the position. Sites interpolated from a map can be off by a minute of arc, which matters when the well is close to your parcel. |
| `well_constructed_depth_feet` / `hole_constructed_depth_feet` / `well_construction_date` | Which aquifer the level represents. |
| `land_surface_altitude_feet` / `_datum` / `_accuracy_feet` / `_method` | Subtract depth from altitude for water-table elevation. |
| `aquifer_code` / `national_aquifer_code` / `aquifer_type_code` | Aquifer identity, where USGS publishes it. |
| `static_depth_to_water_feet` | Most recent **ambient static** depth. Negative = artesian. |
| `static_reading_date` / `_age_years` / `_approval_status` / `_is_provisional` / `_qualifiers` / `_observing_procedure` / `_parameter_code` / `_unit` | Full provenance of that number, including how it was measured. |
| `latest_reading_date` / `_age_years` / `_value_raw` / `_unit` / `_parameter_code` / `_basis` / `_qualifiers` / `_is_static` / `_approval_status` / `_unusable_reason` / `_elevation_datum` | The most recent record of **any** kind. It can be newer than the static reading when the last visit found the well pumping, dry or flowing. `_value_raw` is kept verbatim so nothing fetched is silently discarded. |
| `depth_derived_from_elevation_feet` / `depth_derivation_datum` / `depth_derivation_blocked_reason` | Datum-safe derivation, or the exact reason it was refused. |
| `dry_observed` / `flowing_artesian_observed` / `pumping_observed` / `artesian_static_level` / `unknown_qualifier_seen` | Conditions ever recorded at this well. |
| `measurement_series_rows_seen` / `superseded_series_rows_seen` | How many measurement series USGS returned for this well, and how many were older than the one used. |
| `history_fetched` / `history_readings_fetched` / `history_static_depth_readings` / `history_min_depth_feet` / `history_max_depth_feet` / `history_median_depth_feet` / `history_fluctuation_feet` / `history_earliest_reading` / `history_latest_reading` / `history_truncated` / `history_source_error` | With `includeHistory`: the observed range of water-table movement — the number dewatering and basement design actually need. |

### Use as an MCP tool

This actor is callable by AI agents through Apify's hosted MCP server, so an assistant can
screen a site mid-conversation. Billing is unchanged when it is called as a tool, and a
run that cannot answer fails without billing.

**Claude Desktop / Claude Code / Cursor** — add to your MCP config:

```json
{
  "mcpServers": {
    "apify": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote",
        "https://mcp.apify.com/sse?actors=malonestar/usgs-groundwater-depth-screener"
      ],
      "env": { "APIFY_TOKEN": "<your Apify token>" }
    }
  }
}
```

**A worked agent prompt:**

> "We're doing a Phase I on 1400 Industrial Blvd, Long Island NY (40.7891, -73.1350).
> Screen depth to groundwater within a mile, tell me the shallowest measured water table
> and how old that measurement is, and flag whether any of it is current enough to cite."

**Chaining with the rest of the Phase I shelf** — these compose into one physical-setting
and recognised-environmental-conditions workup for the same coordinate:

```
https://mcp.apify.com/sse?actors=malonestar/usgs-groundwater-depth-screener,malonestar/epa-contaminated-site-screener,malonestar/site-due-diligence-bundle
```

- [`epa-contaminated-site-screener`](https://apify.com/malonestar/epa-contaminated-site-screener)
  — Superfund, RCRA, USTs, spills and ECHO enforcement around the same point. Depth to
  groundwater is what turns a nearby release into a plume question.
- [`site-due-diligence-bundle`](https://apify.com/malonestar/site-due-diligence-bundle)
  — the 20-layer one-call screen (flood, seismic, wetlands, karst, habitat, levees and
  more) with a composite site score.
- [`ssurgo-soil-suitability-screener`](https://apify.com/malonestar/ssurgo-soil-suitability-screener)
  — NRCS soil profile, hydric rating and depth to restrictive layer at the same coordinate.
- [`nhd-surface-water-404-screener`](https://apify.com/malonestar/nhd-surface-water-404-screener)
  — Clean Water Act §404 surface-water jurisdiction nearby.
- [`epa-drinking-water-quality-screener`](https://apify.com/malonestar/epa-drinking-water-quality-screener)
  — SDWA violations and PFAS occurrence for the serving public water systems.
- [`usgs-historical-topo-records-review`](https://apify.com/malonestar/usgs-historical-topo-records-review)
  — historical topographic map coverage for the ASTM historical-use review.

### FAQ

**How do I find the depth to the water table at an address?**
Geocode it, pass the latitude and longitude in `assets`, and read
`nearest_static_depth_to_water_feet` together with `nearest_static_reading_date`. For
design work read `shallowest_depth_to_water_feet` — the conservative value across the
whole radius.

**Why is the measurement so old?**
Because that is genuinely the newest USGS has for that location. Most of the national
groundwater network is measured periodically, and a great many wells were last visited
decades ago. The actor never hides this: `data_currency_class` and `all_readings_stale`
are on every row, and Denver's nearest reading really is from 1962.

**Is this a current water level?**
Only when `data_currency_class` is `current` (under a year). Otherwise it is a historical
measurement — still the best available evidence, and still what a Phase I cites, but it
must be reported with its date.

**What is the seasonal high water table?**
Turn on `includeHistory`. `history_min_depth_feet` is the shallowest level ever recorded
at that well and `history_fluctuation_feet` is the total observed range. A single latest
reading cannot tell you this.

**Why does a well show no depth?**
Check `latest_reading_unusable_reason` and `latest_reading_qualifiers`. The common causes
are a **dry** well (water deeper than the hole — USGS publishes no value), a **flowing**
artesian well (nothing to measure), a **pumping** measurement (drawdown, not the water
table), or a level published as an **elevation** whose datum does not match the well's own
land-surface datum.

**What does a negative depth mean?**
Artesian conditions — the water level stands above land surface. `-81.16` means 81 ft
above ground. These are real USGS values and are never clamped or dropped.

**What if there are no wells nearby?**
`no_monitoring_locations_in_radius` with a count of `0`. That is a verified negative: USGS
answered and has nothing there. It is a different value from `source_unavailable`, which
means USGS did not answer and nothing on the row is a finding.

**Do I need an API key?**
No. The USGS OGC API is keyless. But the anonymous quota is **1,000 requests per hour per
IP address**, and Apify runs from shared addresses, so a busy hour can return HTTP 429
through no fault of your run. A free key from
[api.waterdata.usgs.gov/signup](https://api.waterdata.usgs.gov/signup/) moves you to a
private quota — pass it in the optional `apiKey` field.

**How many rows will a run produce?**
One per site, plus one per monitoring location when `includeWellDetail` is on (capped by
`maxWellsPerAsset`). Every row is a billable result, so leave well detail off for large
portfolio screens.

**Which USGS parameters are used?**
Depth below land surface: `72019` (feet), `30210` and `99019` (metres, converted). Water
level elevation, reported but never implicitly converted to depth: `62610` (NGVD29),
`62611` (NAVD88), `62612`, `62613`, `72150` and `72389` (local mean sea level).

### Source and coverage

- **USGS Water Data OGC API**, `https://api.waterdata.usgs.gov/ogcapi/v0` — collections
  `monitoring-locations`, `latest-field-measurements`, `field-measurements` and
  `parameter-codes`. Public domain, no key required.
- The legacy `waterservices.usgs.gov/nwis/gwlevels` service was **decommissioned on
  2026-06-01**. This actor is built on the OGC API only and deliberately does not fall
  back to it.
- Coverage is the USGS national network — every state and territory, but density varies
  enormously. Urban and agricultural basins are well covered; some rural areas have no
  monitored well within miles. The row always tells you which situation you are in.
- Data is public domain (USGS). Please credit the U.S. Geological Survey when you
  republish it.

### Limitations, stated plainly

- **These are measurements, not a model.** The actor reports what USGS measured at
  specific wells on specific dates. It does not interpolate a water-table surface to your
  parcel, and it does not infer a flow direction.
- **Well depth matters.** A 320 ft bedrock well and a 31 ft alluvial well a mile apart
  will report very different water levels, and both are correct. Use
  `well_constructed_depth_feet` and `national_aquifer_code` to work out which one is
  relevant to your excavation.
- **Positional accuracy varies.** Some sites were located by GPS, others interpolated from
  a paper map to the nearest minute of arc. `well_horizontal_accuracy` is on every row.
- **No result total exists upstream.** This API publishes no `numberMatched`, so
  completeness can only be established structurally. When it cannot be proven,
  `results_truncated` is `true` and the statistics on that row cover a subset.
- This is a screening tool. It does not replace a site-specific hydrogeologic
  investigation, a monitoring well, or a licensed professional's judgment.

### Pricing

Pay per result — one charge per row emitted, with graduated discounts on paid Apify plans.
A run that cannot answer fails loudly and bills nothing.

### Complete field reference

Every field the dataset can contain, exactly as declared in the dataset schema.
All fields except `record_type` tolerate `null`, and `null` always means "this was not checked" rather than "the answer is no".

| Field | Type | Description |
|---|---|---|
| `record_type` | string | asset\_summary or well. One asset\_summary row per screened site; one well row per monitoring location inside the radius when includeWellDetail is on. |
| `asset_label` | string | The label you supplied for this site, echoed on every row so results can be joined back to your portfolio. |
| `asset_lat` | number | The WGS84 latitude that was screened, after numeric validation. |
| `asset_lon` | number | The WGS84 longitude that was screened, after numeric validation. |
| `search_radius_miles` | number | The radius in statute miles actually used for this site. A bbox is used to fetch candidates and every candidate is then re-filtered by true haversine distance, so this is a real circle and not a square. |
| `screening_result` | string | One of: static\_depth\_available (a usable static water level was found), readings\_present\_no\_static\_depth (water-level records exist but none is an ambient static depth), no\_water\_level\_data\_in\_radius (monitoring locations exist but none publishes a water level - a verified negative), no\_monitoring\_locations\_in\_radius (USGS answered and has no sites here - a verified negative), source\_unavailable (USGS did not answer; nothing here is a negative finding). |
| `monitoring_locations_in_radius` | integer | Count of USGS monitoring locations of any site type whose true haversine distance is within the radius. null when the locations source did not answer. |
| `groundwater_wells_in_radius` | integer | Of those, how many carry a GW\* site type (well, test hole, extensometer, hyporheic-zone well and so on). |
| `locations_with_water_level_data` | integer | How many of the locations in radius published at least one water-level record of any kind. |
| `wells_with_static_depth_reading` | integer | How many published a reading usable as an ambient static depth below land surface. This is the denominator behind every depth statistic on this row. |
| `wells_with_elevation_only_reading` | integer | How many published a water-level ELEVATION but no usable depth. Reported so a zero depth count is never mistaken for an absence of groundwater data. |
| `wells_with_depth_derived_from_elevation` | integer | Of those, how many allowed a depth to be derived because the site altitude datum matched the measurement datum exactly. Derived depths never feed the headline fields. |
| `dry_wells_observed` | integer | How many locations in radius have a reading qualified Dry. A dry well means water is deeper than the hole; USGS publishes no value for it and neither does this actor. |
| `pumping_only_wells_observed` | integer | How many have readings only from a pumping well. A pumping level is a drawdown level, not the ambient water table, and is never reported as depth to groundwater. |
| `artesian_wells_observed` | integer | How many have a negative static depth, meaning the water level stands above land surface. USGS publishes these as genuine negative values and they are preserved, not clamped. |
| `static_readings_provisional_count` | integer | How many of the static readings used carry approval\_status other than Approved. Provisional data has not completed USGS review. |
| `nearest_static_depth_to_water_feet` | number | Depth to water in feet below land surface at the NEAREST location that has a usable static reading. This is the headline number. It is a measurement from a specific date, not a current condition - always read it together with nearest\_static\_reading\_age\_years. |
| `nearest_static_reading_date` | string | The date and time that nearest static reading was taken (ISO 8601, UTC). |
| `nearest_static_reading_age_years` | number | How many years old that reading is as of this run. USGS groundwater records are historical: readings decades old are common and are reported as such rather than presented as current. |
| `nearest_static_reading_approval` | string | Approved or Provisional for the nearest static reading. |
| `nearest_static_well_id` | string | USGS monitoring location identifier of that well. |
| `nearest_static_well_name` | string | USGS name of that well. |
| `nearest_static_well_distance_feet` | number | True haversine distance from your coordinate to that well, in feet. |
| `nearest_static_well_bearing_compass` | string | Compass bearing from your coordinate to that well, to 16 points. |
| `nearest_static_well_depth_feet` | number | Constructed depth of that well in feet, where USGS publishes it. Tells you which aquifer the reading represents. |
| `nearest_static_well_aquifer_code` | string | USGS national aquifer code for that well, where published. |
| `shallowest_depth_to_water_feet` | number | The shallowest static depth to water found anywhere in the radius. For excavation, basement and dewatering screening this is usually more conservative than the nearest well. |
| `shallowest_depth_well_id` | string | Monitoring location that produced the shallowest depth. |
| `shallowest_depth_reading_date` | string | Date of the shallowest reading (ISO 8601, UTC). |
| `depth_to_water_min_feet` | number | Minimum static depth to water across every usable reading in the radius. |
| `depth_to_water_max_feet` | number | Maximum static depth to water across every usable reading in the radius. A wide min-max spread usually means multiple aquifers, not measurement error - check the per-well rows. |
| `depth_to_water_median_feet` | number | Median static depth to water across the radius. |
| `depth_readings_used` | integer | How many static depth values the statistics above were computed from. |
| `most_recent_reading_date_in_radius` | string | The single most recent water-level reading of any kind anywhere in the radius (ISO 8601, UTC). The honest answer to "how current is groundwater data here". |
| `most_recent_reading_age_years` | number | Age in years of that most recent reading. |
| `most_recent_reading_well_id` | string | Monitoring location that produced it. |
| `data_currency_class` | string | current (under 1 year), recent (1-5), historical (5-20) or archival (20+), from the most recent reading in the radius. null when nothing was read. |
| `max_age_years_for_current` | number | The maxAgeYearsForCurrent threshold this run used. |
| `readings_within_max_age_count` | integer | How many locations in radius have a latest reading no older than that threshold. |
| `all_readings_stale` | boolean | true when every location in radius has a latest reading older than the threshold - the data exists but describes a past condition. null when no reading was obtained at all. |
| `shallow_groundwater_threshold_feet` | number | The shallowGroundwaterThresholdFeet this run used. |
| `shallow_groundwater_flag` | boolean | true when the shallowest static depth in radius is at or above the threshold. null when no usable static depth was found - a site with no data is never flagged as having deep groundwater. |
| `artesian_conditions_present` | boolean | true when at least one static level in radius stands above land surface. null when the data was not adequate to decide. |
| `phase1_physical_setting_flag` | boolean | true when shallow or artesian groundwater was measured, so ASTM E1527-21 section 8.2.2 physical setting warrants a closer look. GATED: null whenever depth\_data\_adequate\_for\_verdict is false. It never reads false because a source was unavailable. |
| `depth_data_adequate_for_verdict` | boolean | true only when both upstream reads succeeded AND at least one usable static depth was obtained. Every flag above is gated on this. |
| `measurements_source_status` | string | ok or unavailable for the latest-field-measurements read. unavailable means no finding on this row is a negative. |
| `locations_source_status` | string | ok or unavailable for the monitoring-locations read. |
| `history_source_status` | string | ok, partial, unavailable or not\_applicable for the optional per-well history read. null when includeHistory was off. |
| `measurements_source_error` | string | Verbatim error from the measurements read when it failed. |
| `locations_source_error` | string | Verbatim error from the locations read when it failed. |
| `query_complete` | boolean | true only when the cursor walk was PROVEN to have reached the end. This API publishes no numberMatched, so there is no server total to check against; completeness is established structurally from three signals that must agree - the final page carried no next link, the final page was short, and the unique feature ids equal the sum of every numberReturned. |
| `results_truncated` | boolean | The inverse of query\_complete. When true the counts and statistics on this row are computed over a SUBSET and must not be read as exhaustive. |
| `paging_pages_fetched` | integer | How many pages the cursor walk consumed. |
| `paging_rows_returned` | integer | Sum of numberReturned across all pages. |
| `paging_unique_ids` | integer | Count of distinct feature ids collected. A gap between this and paging\_rows\_returned means cursor overlap or a dropped page. |
| `paging_exhaustion_proof` | string | JSON of the three exhaustion signals: noNextLink, shortFinalPage, idsMatchReturned. Published so completeness can be audited rather than trusted. |
| `paging_incomplete_reasons` | string | Why completeness could not be proven, when it could not. |
| `server_total_available` | boolean | Always false, and published deliberately. The USGS OGC API returns no numberMatched, so no free exact total exists for any query against it. |
| `error` | string | Set when this site could not be screened at all, for example an invalid coordinate. |
| `usgs_api_root` | string | The exact API root this row came from. The path is versioned and will move. |
| `usgs_api_version` | string | The OGC API version segment in use. |
| `collections_verified` | integer | How many collections the API advertised when the run started. The four this actor depends on are verified by name before any row is emitted. |
| `parameter_codes_queried` | string | The USGS parameter codes queried: depth-below-land-surface codes plus water-level elevation codes. |
| `drift_checks_ran` | boolean | false only when skipDriftChecks was deliberately set; then nothing on the row is guaranteed to come from an undegraded source. |
| `checked_at` | string | When this run queried USGS (ISO 8601, UTC). |
| `api_rate_limit_remaining` | integer | USGS x-ratelimit-remaining after this site. MEASURED: USGS sends this header only on an HTTP 429 response, not on successful ones, so on a healthy run it is legitimately null - null means "not reported", never "zero requests left". It populates when a run is being throttled. The anonymous quota is 1,000 requests per hour PER IP and Apify runs from shared addresses, so supplying your own free apiKey moves you to a private quota. |
| `api_key_supplied` | boolean | Whether this run used your own USGS API key. |
| `well_rank_by_distance` | integer | Rank of this monitoring location among those in radius, 1 = nearest. |
| `well_id` | string | USGS monitoring location identifier. |
| `well_agency_code` | string | Agency that operates the site. |
| `well_site_number` | string | USGS site number without the agency prefix. |
| `well_name` | string | USGS site name. Often a survey-grid code rather than a plain-language name. |
| `well_site_type_code` | string | GW (well), GW-TH (test hole), GW-MW (multiple wells), GW-EX, GW-HZ, GW-CR, GW-IW, or a non-groundwater type. A 72019 reading at a non-GW site is reported rather than hidden. |
| `well_site_type` | string | Plain-language site type. |
| `well_state` | string | State the site is in. |
| `well_county` | string | County the site is in. |
| `well_hydrologic_unit_code` | string | USGS hydrologic unit code (HUC) of the watershed the site sits in. |
| `well_lat` | number | WGS84 latitude of the site as published by USGS. |
| `well_lon` | number | WGS84 longitude of the site as published by USGS. |
| `well_distance_feet` | number | True haversine distance from your coordinate, in feet. |
| `well_distance_miles` | number | The same distance in statute miles. |
| `well_bearing_degrees` | number | Bearing from your coordinate to the site, degrees clockwise from true north. |
| `well_bearing_compass` | string | The same bearing as a 16-point compass label. |
| `well_horizontal_accuracy` | string | How precisely USGS knows the site position. Sites interpolated from a map can be off by a minute of arc, which matters when the site is close to your parcel. |
| `well_horizontal_method` | string | How the position was determined. |
| `well_constructed_depth_feet` | number | Constructed depth of the well in feet - which aquifer the water level represents. |
| `hole_constructed_depth_feet` | number | Depth of the drilled hole in feet, where published. |
| `well_construction_date` | string | Construction date, where published. |
| `land_surface_altitude_feet` | number | Land-surface altitude at the site in feet. Subtract the depth to water to get the water-table elevation. |
| `land_surface_altitude_datum` | string | The vertical datum that altitude is referenced to, usually NAVD88 or NGVD29. The two differ by a location-specific offset and are never reconciled silently. |
| `land_surface_altitude_accuracy_feet` | number | Stated accuracy of the altitude. |
| `land_surface_altitude_method` | string | How the altitude was determined. |
| `aquifer_code` | string | Local aquifer code, where published. |
| `national_aquifer_code` | string | USGS national aquifer code, where published. |
| `aquifer_type_code` | string | Aquifer type code (confined, unconfined and so on), where published. |
| `static_depth_to_water_feet` | number | The most recent AMBIENT STATIC depth to water at this site, in feet below land surface. Only a reading qualified Static counts. A negative value is real and means the water level stands above land surface (artesian). |
| `static_reading_date` | string | Date and time of that static reading (ISO 8601, UTC). |
| `static_reading_age_years` | number | Age of that static reading in years at run time. |
| `static_reading_approval_status` | string | Approved or Provisional. |
| `static_reading_is_provisional` | boolean | true when the static reading has not completed USGS review. |
| `static_reading_qualifiers` | string | USGS condition qualifiers on that reading, joined with +. |
| `static_reading_observing_procedure` | string | How it was measured - steel tape, electric tape, pressure transducer and so on. |
| `static_reading_parameter_code` | string | USGS parameter code behind the static depth. 72019 is feet below land surface. |
| `static_reading_unit` | string | Unit as published by USGS for that reading. |
| `latest_reading_date` | string | Date of the most recent water-level record of ANY kind at this site, static or not (ISO 8601, UTC). It can be newer than static\_reading\_date when the latest visit found the well pumping, dry or flowing. |
| `latest_reading_age_years` | number | Age of that latest reading in years. |
| `latest_reading_value_raw` | string | The value exactly as USGS published it, as a string. Kept verbatim so nothing fetched is silently discarded. It is null on every Dry and Flowing record - a null here is not a zero. |
| `latest_reading_unit` | string | Unit of that raw value. |
| `latest_reading_parameter_code` | string | USGS parameter code of the latest reading. |
| `latest_reading_basis` | string | measured\_depth\_below\_land\_surface or water\_level\_elevation. An elevation is not a depth and is never treated as one. |
| `latest_reading_qualifiers` | string | Condition qualifiers on the latest reading, joined with +. Measured values include Static, Above+Pumping, NoMeasurement+Dry and NoMeasurement+Flowing. |
| `latest_reading_is_static` | boolean | true only for an ambient static level. false when a qualifier rules it out (pumping, dry, flowing, surface-water affected, foreign substance). null when the qualifier set was empty or unrecognised - an unknown qualifier is never assumed to be static. |
| `latest_reading_approval_status` | string | Approved or Provisional for the latest reading. |
| `latest_reading_unusable_reason` | string | Why the latest reading yielded no depth, when it did not - for example "no value published (qualifier NoMeasurement+Dry)". |
| `latest_reading_elevation_datum` | string | For an elevation reading, the datum it is referenced to: NAVD88, NGVD29 or LMSL. |
| `depth_derived_from_elevation_feet` | number | Depth derived as land-surface altitude minus water-level elevation, and ONLY when the site altitude datum matches the measurement datum exactly. Never feeds the headline depth fields. |
| `depth_derivation_datum` | string | The datum both values shared when a derivation was possible. |
| `depth_derivation_blocked_reason` | string | Why a depth could not be derived from an elevation reading - most often a NGVD29 versus NAVD88 datum mismatch, whose offset is location-specific and is never assumed. |
| `dry_observed` | boolean | true when any reading at this site is qualified Dry - the tape reached the bottom without finding water, so the water table is DEEPER than the hole. USGS publishes no value for it. null when there were no readings at all. |
| `flowing_artesian_observed` | boolean | true when any reading is qualified Flowing - the well was discharging at surface, so no depth could be measured. |
| `pumping_observed` | boolean | true when any reading was taken while the well was pumping. Such a level is drawn down and is excluded from every depth statistic. |
| `artesian_static_level` | boolean | true when the static depth is negative, meaning the water level stands above land surface. |
| `unknown_qualifier_seen` | boolean | true when USGS returned a condition qualifier outside the verified closed vocabulary this actor ships. Such a reading is never classified as static. |
| `measurement_series_rows_seen` | integer | How many rows the latest-field-measurements collection returned for this site. It returns one row per measurement SERIES, and a site can have several. |
| `superseded_series_rows_seen` | integer | How many of those rows were older than the one used. The collection is NOT ordered, so the newest is selected by comparing timestamps rather than by taking the first row. |
| `history_fetched` | boolean | Whether a full reading history was retrieved for this site (includeHistory, nearest historyWellCount sites only). |
| `history_readings_fetched` | integer | How many historical records were retrieved. |
| `history_static_depth_readings` | integer | How many of them were usable static depths. |
| `history_min_depth_feet` | number | Shallowest static depth in the retrieved history - the seasonal high water table. |
| `history_max_depth_feet` | number | Deepest static depth in the retrieved history. |
| `history_median_depth_feet` | number | Median static depth across the retrieved history. |
| `history_fluctuation_feet` | number | max minus min - the observed range of water-table movement, which is what dewatering and basement design actually need. |
| `history_earliest_reading` | string | Earliest record in the retrieved history (ISO 8601, UTC). |
| `history_latest_reading` | string | Latest record in the retrieved history (ISO 8601, UTC). |
| `history_truncated` | boolean | true when more history exists than was retrieved. The history query is sorted, and this API cannot page a sorted query, so it is deliberately a single page and says so. |
| `history_source_error` | string | Verbatim error when the history read failed for this site. |

# Actor input Schema

## `assets` (type: `array`):

One object per site: {"lat": 40.7891, "lon": -73.1350, "label": "Long Island NY site", "radiusMiles": 1}. lat/lon are WGS84 decimal degrees and are validated numerically before any query is built. label is free text echoed on every output row. radiusMiles is optional and overrides the run-level radius for that site only.

## `radiusMiles` (type: `integer`):

Run-level search radius in statute miles, used for any site that does not carry its own radiusMiles. Accepts 1 to 25; ASTM E1527-21 physical-setting review commonly uses 0.5 or 1 mile. If omitted the actor uses 1 mile. A square bounding box fetches candidates and every candidate is then re-filtered by true haversine distance, so this is a real circle.

## `includeWellDetail` (type: `boolean`):

When true, each site summary row is followed by one row per USGS monitoring location inside the radius, nearest first, with the well's depth, aquifer, land-surface altitude and datum, its most recent static water level, that reading's date, age, approval status and condition qualifiers. When false (the actor's behaviour if omitted) only the one summary row per site is emitted. Every row is a billable result, so leave this off for large-radius portfolio screens.

## `maxWellsPerAsset` (type: `integer`):

Caps how many per-location detail rows are emitted for each site when includeWellDetail is on. Locations are always emitted nearest-first, so the cap keeps the closest ones. If omitted the actor uses 50.

## `maxAssets` (type: `integer`):

Safety cap on how many sites a single run will screen. Extra sites are skipped with a warning in the log. If omitted the actor uses 25.

## `includeHistory` (type: `boolean`):

When true the actor also retrieves the complete depth-to-water record for the nearest few locations and reports the observed minimum, maximum, median and total fluctuation - the seasonal range dewatering and basement design actually need, which a single latest reading cannot give you. Costs one extra USGS request per well. If omitted this is off.

## `historyWellCount` (type: `integer`):

Number of nearest locations (that have any water-level data) to retrieve a full history for when includeHistory is on. If omitted the actor uses 3.

## `maxAgeYearsForCurrent` (type: `integer`):

USGS groundwater records are historical - many wells were last measured decades ago. A reading older than this many years is counted as stale, and a site where EVERY reading is older gets all\_readings\_stale = true. This never suppresses a reading; it labels it. If omitted the actor uses 5 years.

## `shallowGroundwaterThresholdFeet` (type: `integer`):

Depth at or above which the shallowest measured water level raises shallow\_groundwater\_flag. If omitted the actor uses 15 feet. The flag is null - never false - whenever no usable static depth was found, so a site with no data is never reported as having deep groundwater.

## `apiKey` (type: `string`):

Optional. The USGS OGC API is keyless, but the anonymous quota is 1,000 requests per hour PER IP ADDRESS and Apify runs from shared addresses, so a busy hour can return HTTP 429 through no fault of your run. A free key from https://api.waterdata.usgs.gov/signup/ moves you to a private quota. Leave blank to use the anonymous quota.

## `skipDriftChecks` (type: `boolean`):

Diagnostic only. The actor normally verifies the live API before emitting anything: the four required collections are present, parameter code 72019 still means depth below land surface in feet, two pinned canary wells return their pinned identity and earliest measurement, a mid-Pacific negative control returns zero, and a cursor walk is proved to terminate. Skipping them removes the guarantee that the answers came from an undegraded source.

## `simulateOutage` (type: `string`):

Permanent diagnostic seam used to prove the failure path without waiting for a real outage. 'measurements' fails the water-level read, 'locations' fails the monitoring-location read, 'both' fails both, 'none' is the normal behaviour. With 'both' the run fails loudly and emits nothing.

## Actor input object example

```json
{
  "assets": [
    {
      "lat": 40.7891,
      "lon": -73.135,
      "label": "Long Island NY - current USGS water-level network",
      "radiusMiles": 1
    },
    {
      "lat": 39.7392,
      "lon": -104.9903,
      "label": "Denver CO downtown - data exists but is archival",
      "radiusMiles": 1
    },
    {
      "lat": 29.7604,
      "lon": -95.3698,
      "label": "Houston TX - Gulf Coast aquifer, historical record",
      "radiusMiles": 1
    },
    {
      "lat": 30,
      "lon": -140,
      "label": "Mid-Pacific control - genuinely no USGS wells",
      "radiusMiles": 1
    }
  ],
  "radiusMiles": 1,
  "includeWellDetail": true,
  "maxWellsPerAsset": 8,
  "maxAssets": 25,
  "includeHistory": true,
  "historyWellCount": 2,
  "maxAgeYearsForCurrent": 5,
  "shallowGroundwaterThresholdFeet": 15,
  "simulateOutage": "none"
}
```

# Actor output Schema

## `results` (type: `string`):

The default dataset.

# 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 = {
    "assets": [
        {
            "lat": 40.7891,
            "lon": -73.135,
            "label": "Long Island NY - current USGS water-level network",
            "radiusMiles": 1
        },
        {
            "lat": 39.7392,
            "lon": -104.9903,
            "label": "Denver CO downtown - data exists but is archival",
            "radiusMiles": 1
        },
        {
            "lat": 29.7604,
            "lon": -95.3698,
            "label": "Houston TX - Gulf Coast aquifer, historical record",
            "radiusMiles": 1
        },
        {
            "lat": 30,
            "lon": -140,
            "label": "Mid-Pacific control - genuinely no USGS wells",
            "radiusMiles": 1
        }
    ],
    "radiusMiles": 1,
    "includeWellDetail": true,
    "maxWellsPerAsset": 8,
    "maxAssets": 25,
    "includeHistory": true,
    "historyWellCount": 2,
    "maxAgeYearsForCurrent": 5,
    "shallowGroundwaterThresholdFeet": 15,
    "skipDriftChecks": false,
    "simulateOutage": "none"
};

// Run the Actor and wait for it to finish
const run = await client.actor("malonestar/usgs-groundwater-depth-screener").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 = {
    "assets": [
        {
            "lat": 40.7891,
            "lon": -73.135,
            "label": "Long Island NY - current USGS water-level network",
            "radiusMiles": 1,
        },
        {
            "lat": 39.7392,
            "lon": -104.9903,
            "label": "Denver CO downtown - data exists but is archival",
            "radiusMiles": 1,
        },
        {
            "lat": 29.7604,
            "lon": -95.3698,
            "label": "Houston TX - Gulf Coast aquifer, historical record",
            "radiusMiles": 1,
        },
        {
            "lat": 30,
            "lon": -140,
            "label": "Mid-Pacific control - genuinely no USGS wells",
            "radiusMiles": 1,
        },
    ],
    "radiusMiles": 1,
    "includeWellDetail": True,
    "maxWellsPerAsset": 8,
    "maxAssets": 25,
    "includeHistory": True,
    "historyWellCount": 2,
    "maxAgeYearsForCurrent": 5,
    "shallowGroundwaterThresholdFeet": 15,
    "skipDriftChecks": False,
    "simulateOutage": "none",
}

# Run the Actor and wait for it to finish
run = client.actor("malonestar/usgs-groundwater-depth-screener").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 '{
  "assets": [
    {
      "lat": 40.7891,
      "lon": -73.135,
      "label": "Long Island NY - current USGS water-level network",
      "radiusMiles": 1
    },
    {
      "lat": 39.7392,
      "lon": -104.9903,
      "label": "Denver CO downtown - data exists but is archival",
      "radiusMiles": 1
    },
    {
      "lat": 29.7604,
      "lon": -95.3698,
      "label": "Houston TX - Gulf Coast aquifer, historical record",
      "radiusMiles": 1
    },
    {
      "lat": 30,
      "lon": -140,
      "label": "Mid-Pacific control - genuinely no USGS wells",
      "radiusMiles": 1
    }
  ],
  "radiusMiles": 1,
  "includeWellDetail": true,
  "maxWellsPerAsset": 8,
  "maxAssets": 25,
  "includeHistory": true,
  "historyWellCount": 2,
  "maxAgeYearsForCurrent": 5,
  "shallowGroundwaterThresholdFeet": 15,
  "skipDriftChecks": false,
  "simulateOutage": "none"
}' |
apify call malonestar/usgs-groundwater-depth-screener --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,malonestar/usgs-groundwater-depth-screener"
        }
    }
}

```

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/bEa8rGXpWFe2XiCOX/builds/fAQqpccotwxRve2ue/openapi.json
