# NHD Surface Water & Section 404 Wetland Screener (`malonestar/nhd-surface-water-404-screener`) Actor

Screen any lat/lon against USGS NHDPlus HR surface water. 94 fields: exact distance to the nearest perennial, intermittent and ephemeral reach, waterbody type and purpose, mean annual flow, stream order, HUC-8/10/12, a jurisdictional-likelihood call with its reasoning, and a Section 404/WOTUS flag.

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

## Pricing

from $6.60 / 1,000 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

## NHD Surface Water & Section 404 Screener

Give it a list of site coordinates. Get back, per site, the answer that drives a
Clean Water Act call: **how far is the nearest *perennial* stream, versus the
nearest *intermittent* one, versus the nearest *ephemeral* wash** — plus the
mean annual flow on that reach, the nearest lake, reservoir or swamp/marsh, the
stream order and drainage area of the receiving water, the HUC8/10/12 the site
drains through, and a `HIGH` / `MEDIUM` / `LOW` Section 404 review flag that
tells you **which feature drove it and why**.

Data comes from the **USGS NHDPlus HR** MapServer
(`hydro.nationalmap.gov/arcgis/rest/services/NHDPlus_HR/MapServer`) — keyless,
public-domain, no login, no scraping of a rendered page.

***

### Why the perennial / intermittent / ephemeral split matters

Most surface-water tools dump every NHD flowline within a buffer and leave you
to sort it out. After *Sackett v. EPA* (2023) the jurisdictional question turns
on **relatively permanent flow with a continuous surface connection**, so this
actor does the sorting — and reports the evidence.

| FCode | Regime | Raises the 404 flag? |
| --- | --- | --- |
| 46006 | Perennial stream | Yes |
| 46003 | Intermittent stream | Yes |
| 46000 | Stream/river, unspecified | Yes |
| 55800 | Artificial path (mapped centerline of a river or lake) | Yes |
| 46007 | **Ephemeral** wash | **No** |
| 33600 / 33601 / 33603 | Canal / ditch / aqueduct / stormwater | No |
| 42800–42824 | Pipeline | No |
| 42000–42003 | Underground conduit | No |
| 46800 | Drainageway | No |

Polygons are treated the same way. Lakes, ponds, reservoirs, swamp/marsh,
estuaries, bays, foreshore, rapids and submerged streams raise the flag.
**Washes (FType 484), playas, ice masses, ditch polygons and engineered
structures — levees, bridges, dams, spillways, lock chambers, flumes, water
intake/outflow, mapped inundation areas — do not.** They are still reported, in
their own columns, so you can see what was excluded and why.

**Flag bands:** `HIGH` = a jurisdictional feature within **100 ft (30.48 m)**.
`MEDIUM` = within **300 ft (91.44 m)**. `LOW` = everything else.

A site genuinely far from mapped water is a **legitimate result**: you get a row
with null distances, `LOW`, and `jurisdictional_likelihood: UNLIKELY` — not an
error and not a missing row.

#### The flag can also be `null`, and that is the point

`section_404_review_flag` is `null` whenever a contributing NHD layer did not
answer for that site. A `LOW` or `MEDIUM` verdict asserts that *nothing
jurisdictional lies within 100 ft*; if the waterbody layer never responded, that
negative was never verified, and shipping it as `LOW` would be a confident wrong
answer on a determination that carries liability. A `HIGH` is different — a
positive finding cannot be reversed by missing data — so a `HIGH` stands and is
marked `positive_despite_partial_coverage`.

Every row carries `screening_status`, `layers_ok`, `layers_unavailable`, a
`layer_*_status` per NHD layer, and `section_404_flag_reason` in plain English.
**Filter on `screening_status = "complete"` before acting on any negative
finding.** If the load-bearing flowline layer is down for every site, the run
**fails** rather than emitting rows — nothing is emitted and nothing is billed.

***

### Who buys this

- **Phase I ESA consultants** — the surface-water and adjacent-waters section of
  an ASTM E1527-21 report, with a citable reach code, the NHD feature date, and
  a date-of-source stamp for every row.
- **Wetland delineators** — `nearest_swamp_marsh_dist_m` is NHD's wetland proxy
  and the strongest single signal that a formal delineation is in scope; the
  per-regime feature census (`perennial_features_in_radius` and friends) is what
  you price the field effort from.
- **§404 / Clean Water Act permitting teams** — first-pass triage of which
  parcels need a delineation and an approved jurisdictional determination before
  a Nationwide vs Individual Permit path is chosen. `mean_annual_flow_cfs` is
  the number a relative-permanence argument is actually built on.
- **Solar, BESS and data-center siting teams** — rank hundreds of candidate
  parcels by 404 exposure before spending money on field work. The wash and
  ditch exclusions matter enormously in the arid south-west, where washes are
  the most common mapped NHDArea feature.
- **Land developers and title / diligence shops** — flag creek-adjacent parcels
  across an acquisition pipeline in one run.

***

### Example input

```json
{
  "assets": [
    { "lat": 40.0143,  "lon": -105.2829,  "label": "Boulder CO - creekside redevelopment parcel" },
    { "lat": 29.99091, "lon": -89.93323,  "label": "New Orleans East LA - swamp/marsh adjacent site" },
    { "lat": 33.42931, "lon": -111.98414, "label": "Tempe AZ - parcel inside a mapped desert wash" },
    { "lat": 35.2,     "lon": -115.9,     "label": "Mojave NP CA - upland solar reference site" }
  ],
  "radiusMeters": 1000,
  "maxResults": 100,
  "includeNonNetworkFlowlines": true
}
```

`assets` is **required** — the run is rejected before it starts if it is
missing, not an array, or empty, so nothing is consumed. `lat`/`lon` are decimal
degrees (`latitude`/`longitude` and `lng` are accepted as aliases).
`radiusMeters` is clamped to 50–8000, default 1600.

Those four prefilled sites deliberately cover the range of outcomes: a creekside
parcel 5.2 m from a perennial reach carrying 102.7 cfs (`HIGH`, `LIKELY`); a
Louisiana site sitting *inside* an NHD swamp/marsh (`HIGH`, wetland-driven); an
Arizona parcel sitting inside a 2.54 km² mapped desert wash, which comes back
`LOW` because washes are reported but are **not** jurisdictional after *Sackett*
(the nearest actually-jurisdictional feature is the Salt River at 101 m, just
outside the 300 ft band); and an upland Mojave parcel whose only nearby feature
is an ephemeral reach 258 m away (`LOW`, `UNLIKELY`).

### Example output row (trimmed)

```json
{
  "asset_label": "Boulder CO - creekside redevelopment parcel",
  "lat": 40.0143,
  "lon": -105.2829,
  "search_radius_m": 1000,

  "section_404_review_flag": "HIGH",
  "section_404_flag_status": "complete",
  "jurisdictional_likelihood": "LIKELY",
  "jurisdiction_basis": "A perennial reach lies 5.2 m (17.2 ft) from the site, inside the 100 ft band. NHDPlus reports a mean annual flow of 102.69 cfs on that reach, which supports a relatively permanent flow characterisation under Sackett v. EPA. A canal or ditch lies 364.4 m away and was excluded from the flag.",

  "nearest_jurisdictional_source": "flowline",
  "nearest_jurisdictional_name": "Boulder Creek",
  "nearest_jurisdictional_dist_m": 5.2,
  "nearest_jurisdictional_dist_ft": 17.2,

  "nearest_flowline_name": "Boulder Creek",
  "flow_regime": "perennial",
  "nearest_flowline_fcode_label": "Stream/River: Hydrographic Category = Perennial",
  "nearest_flowline_dist_m": 5.2,
  "mean_annual_flow_cfs": 102.689,
  "mean_annual_velocity_fps": 1.6366,
  "stream_order": 6,
  "drainage_area_sqkm": 336.052,
  "total_upstream_length_km": 705.632,
  "reachcode": "10190005000085",
  "nearest_flowline_data_date": "2012-02-18",

  "nearest_perennial_dist_m": 5.2,
  "nearest_intermittent_dist_m": 412.6,
  "nearest_ephemeral_dist_m": null,
  "perennial_features_in_radius": 4,
  "intermittent_features_in_radius": 2,
  "canal_ditch_features_in_radius": 6,

  "nearest_waterbody_name": "Varsity Lake",
  "nearest_waterbody_type": "Lake/Pond (perennial, closed lake)",
  "nearest_waterbody_dist_m": 839.6,
  "nearest_waterbody_is_jurisdictional": true,
  "nearest_swamp_marsh_dist_m": null,

  "huc8": "10190005",
  "huc10": "1019000504",
  "huc12": "101900050406",
  "huc12_name": "City of Boulder-Boulder Creek",
  "huc12_type": "Standard",
  "tohuc": "101900050601",

  "screening_status": "complete",
  "layers_ok": 5,
  "layers_unavailable": null,
  "distance_method": "exact_geometry",
  "error": null,
  "source_url": "https://hydro.nationalmap.gov/.../MapServer/3/query?...",
  "retrieved_at": "2026-07-28T18:24:57.974Z"
}
```

***

### Output fields

**All 94 fields are listed below** — every field this actor declares, named in full. Distances are
reported in metres and in feet; the `_ft` column is the exact same measurement, converted, so a
permit form and a US survey can both be filled straight from the row.

**Your site, echoed back**

| Field | Meaning |
| --- | --- |
| `asset_label` | Your own label for the site, echoed verbatim so results join back to your parcel list |
| `lat` | Site latitude in decimal degrees (WGS84/NAD83); null on a row whose input coordinate was unusable |
| `lon` | Site longitude in decimal degrees (negative in the USA) |
| `search_radius_m` | Radius in metres actually searched around the site for flowlines, waterbodies and water areas |

**Verdict and audit trail**

| Field | Meaning |
| --- | --- |
| `section_404_review_flag` | `HIGH` / `MEDIUM` / `LOW`, or `null` when a contributing layer did not answer |
| `section_404_flag_status` | `complete`, `positive_despite_partial_coverage`, `indeterminate_partial_coverage`, `indeterminate_probe_failure`, `not_screened` |
| `section_404_flag_reason` | Why the flag was suppressed or qualified; null on a clean row |
| `jurisdictional_likelihood` | `LIKELY` / `POSSIBLE` / `UNLIKELY` / `INDETERMINATE` — a screening opinion, not a determination |
| `jurisdiction_basis` | Full reasoning: which feature, at what distance, with what flow, and what was excluded |
| `nearest_jurisdictional_dist_m` | The distance the flag bands were actually applied to, in metres |
| `nearest_jurisdictional_dist_ft` | The same distance in feet |
| `nearest_jurisdictional_source` | Whether a flowline or a polygon drove the flag |
| `nearest_jurisdictional_name` | Name of the feature that drove the flag |
| `nearest_jurisdictional_type` | Type of the feature that drove the flag |

**Nearest flowline**

| Field | Meaning |
| --- | --- |
| `nearest_flowline_name` | Nearest reach of any type; falls back to reach code — never blank |
| `nearest_flowline_ftype` | Decoded NHD FType of that reach |
| `nearest_flowline_fcode` | Raw numeric NHD FCode |
| `nearest_flowline_fcode_label` | The verbatim USGS FCode description, straight from the layer's coded-value domain |
| `flow_regime` | `perennial` / `intermittent` / `ephemeral` / `artificial_path` / `canal_ditch` / `pipeline` / `underground_conduit` / … |
| `nearest_flowline_is_jurisdictional` | Whether that reach is of a type that can raise the flag |
| `nearest_flowline_dist_m` | Distance to that reach in metres |
| `nearest_flowline_dist_ft` | Distance to that reach in feet |
| `nearest_flowline_layer` | `3` (network, carries NHDPlus VAA) or `4` (non-network, no flow/order/drainage data exists) |
| `mean_annual_flow_cfs` | NHDPlus `qama` — the relative-permanence proxy |
| `mean_annual_velocity_fps` | NHDPlus `vama` |
| `reach_slope` | Smoothed NHDPlus slope for the reach |
| `stream_order` | Strahler stream order |
| `drainage_area_sqkm` | Total upstream drainage area |
| `total_upstream_length_km` | Arbolate sum — total upstream network length |
| `is_headwater` | Network start — nothing upstream |
| `reachcode` | NHD reach code, to cite in a permit or delineation |
| `nearest_flowline_gnis_id` | GNIS identifier of the named reach |
| `nearest_flowline_length_km` | Length of the mapped reach |
| `nearest_flowline_data_date` | Vintage of the mapped feature |
| `nhd_resolution` | Source resolution of the mapped feature |

**Flow-regime distances and census**

| Field | Meaning |
| --- | --- |
| `nearest_perennial_dist_m` / `nearest_perennial_dist_ft` | Nearest FCode 46006, metres and feet |
| `nearest_intermittent_dist_m` / `nearest_intermittent_dist_ft` | Nearest FCode 46003, metres and feet |
| `nearest_ephemeral_dist_m` / `nearest_ephemeral_dist_ft` | Nearest FCode 46007 — reported, never flagged |
| `nearest_canal_ditch_dist_m` | Nearest ditch or canal — reported, never flagged |
| `perennial_features_in_radius` | Perennial reaches within the radius |
| `intermittent_features_in_radius` | Intermittent reaches within the radius |
| `ephemeral_features_in_radius` | Ephemeral reaches within the radius |
| `canal_ditch_features_in_radius` | Canals and ditches within the radius |
| `pipeline_conduit_features_in_radius` | Pipelines and underground conduits within the radius |
| `flowline_features_in_radius` | All flowlines within the radius |
| `waterbody_features_in_radius` | All waterbody polygons within the radius |
| `surface_water_features_in_radius` | Every surface-water feature within the radius — the density signal a delineation scope is priced from |

**Waterbodies and wetlands**

| Field | Meaning |
| --- | --- |
| `nearest_waterbody_name` | Nearest polygon **of any type** — which on an arid site is often a wash |
| `nearest_waterbody_type` | Decoded type of that polygon |
| `nearest_waterbody_dist_m` / `nearest_waterbody_dist_ft` | Distance to it, metres and feet |
| `nearest_waterbody_area_sqkm` | Mapped area of that polygon |
| `nearest_waterbody_fcode` | Raw numeric NHD FCode of the polygon |
| `nearest_waterbody_fcode_label` | Verbatim USGS FCode description for the polygon |
| `nearest_waterbody_purpose` | NHDPlus purpose code (tailings, settling, sewage treatment, cooling, evaporator, disposal, …) |
| `nearest_waterbody_reachcode` | Reach code of the polygon, where NHD carries one |
| `nearest_waterbody_data_date` | Vintage of the polygon |
| `nearest_waterbody_layer` | Which source layer the polygon came from (NHDWaterbody or NHDArea) |
| `nearest_waterbody_is_jurisdictional` | Whether that polygon can raise the flag |
| `site_inside_waterbody` | The site coordinate falls inside the polygon |
| `nearest_jurisdictional_waterbody_name` | Nearest polygon that **can** raise the flag |
| `nearest_jurisdictional_waterbody_type` | Its decoded type |
| `nearest_jurisdictional_waterbody_dist_m` | Distance to it in metres |
| `nearest_swamp_marsh_dist_m` | Nearest NHD Swamp/Marsh (FType 466) — the wetland-delineation trigger |
| `nearest_swamp_marsh_name` | Name of that swamp or marsh, falling back to its NHD identifier |
| `nearest_lake_pond_dist_m` | Nearest lake or pond |
| `nearest_reservoir_dist_m` | Nearest reservoir |
| `nearest_reservoir_type` | Reservoir type: tailings pond, settling pond, sewage treatment pond, cooling pond, evaporator, disposal and so on |
| `nearest_wash_playa_dist_m` | Nearest wash or playa — reported for transparency, deliberately excluded from the flag |

**Watershed**

| Field | Meaning |
| --- | --- |
| `huc8` | Subbasin — what most permit forms ask for |
| `huc10` | Watershed |
| `huc12` | Subwatershed |
| `huc12_name` | Subwatershed name |
| `tohuc` | The HUC12 this subwatershed drains into |
| `tohuc_is_terminal` | Whether the outlet is terminal (`OCEAN`, `CANADA`, `MEXICO`) |
| `huc12_type` | `Standard`, `Closed Basin`, `Frontal`, `Multiple Outlet`, `Water`, `Island`, `Urban`, `Indeterminate` |
| `huc12_area_sqkm` | Subwatershed area |
| `huc12_noncontributing_area_sqkm` | The part of it that does not drain to its outlet |

**Coverage and QA**

| Field | Meaning |
| --- | --- |
| `screening_status` | `complete` / `partial` / `failed` |
| `layer_network_flowline_status` | `ok` / `unavailable` / `not_requested` for NetworkNHDFlowline |
| `layer_nonnetwork_flowline_status` | Same, for NonNetworkNHDFlowline |
| `layer_waterbody_status` | Same, for NHDWaterbody |
| `layer_nhdarea_status` | Same, for NHDArea |
| `layer_wbdhu12_status` | Same, for WBDHU12 |
| `layers_ok` | How many of the five layers answered |
| `layers_unavailable` | Which ones did not |
| `distance_method` | `exact_geometry`, `ring_probe_bound`, `no_features_in_radius`, `unavailable` |
| `unmapped_codes` | Any NHD code USGS returned that this actor does not decode — surfaced, never dropped |
| `error` | Null on a clean row; reason or `PARTIAL:` notice otherwise |
| `source_url` | The exact query used |
| `retrieved_at` | UTC run timestamp |

***

### How the distances are computed

Exact point-to-polyline and point-to-polygon distance from the geometry USGS
returns, in WGS84, with an equirectangular projection anchored at your site
(sub-metre accurate over kilometres). A site inside a polygon is distance `0`.

If NHD ever returns features **without** geometry, no exact distance exists. The
actor then falls back to attribute-only ring probes at exactly 30.48 m and
91.44 m to decide the band honestly, sets `distance_method` to
`ring_probe_bound`, and leaves every `*_dist_m` field **null** — because a probe
gives you a bound, not a distance, and reporting a bound as a distance would be
a fabricated number.

#### The projection trap, and the live assertion that guards it

NHDPlus HR answers in **Web Mercator (wkid 102100) by default**. Without an
explicit `outSR=4326` every coordinate comes back as metres-from-Greenwich, and
every distance derived from it is wrong by orders of magnitude while looking
entirely plausible. An offline test cannot catch that, because the fixture would
have been shaped from the bad data.

So every geometry-bearing response is checked **at runtime**, on three
independent signals. If any of them fails the run fails loudly rather than
emitting distances:

1. the **declared** spatial reference is 4326 — the real signal, and the only
   one of the three that also catches a near-identical datum swap such as
   NAD83 / 4269, where every coordinate still looks correct;
2. every vertex is a possible WGS84 coordinate **by magnitude** (longitude
   within ±180, latitude within ±90). Web Mercator is metres, so an x of
   ≈ −11,721,894 fails this instantly and unambiguously;
3. each returned feature's **own bounding envelope** is within a metre
   allowance (3× the search radius, 5 km floor) of the queried point — which
   catches a response describing the wrong place.

**Coastal note (fixed 2026-07-28, build 1.1.7).** Signal 3 used to be "every
returned *vertex* must be within about a degree of the site", and that is simply
false on an open coast. NHDArea layer 8 models the Gulf of Mexico as a **single
364,494-vertex polygon** spanning lon −89.861 … −88.013 / lat 29.250 … 30.433
(10,021 km²), so a perfectly valid WGS84 response at a Biloxi MS waterfront
parcel contained the real vertex `[−89, 29.25]` — 130 km south of the site — and
the run hard-failed with a false "projection drift". Every open-coast, bay,
sound and large-lake site was unscreenable. A large polygon legitimately *has*
distant vertices; what it may not do is have its **entire extent** sit far from
the point the buffer was drawn around, which is what the envelope test now
asserts. The Mercator case is caught by magnitude, where it always belonged.

Four more live checks run once, before any site is screened, and fail the run if
they trip:

1. every layer id still maps to its expected layer name (a USGS renumbering
   would otherwise make the actor query `FlowDirection` and call it "flowlines");
2. the flowline FCode subtype domain still carries 46003 / 46006 / 46007, the
   codes the whole perennial / intermittent / ephemeral split rests on;
3. a fixed canary query at Boulder Creek still returns features, in 4326, near
   the canary point, **with NHDPlus value-added attributes attached** — so a
   silent loss of `qama` / `streamorde` / `totdasqkm` surfaces as a red run
   instead of a column that quietly renders null on every row;
4. NHDArea FType 484 (Wash) still exists, since the non-jurisdictional wash
   handling depends on it.

***

### Use as an MCP tool

This actor is available over the Apify MCP server, so an AI agent (Claude
Desktop, Claude Code, Cursor, or any MCP client) can call it directly. Point
your MCP client at `https://mcp.apify.com` and expose the actor
`malonestar/nhd-surface-water-404-screener`. A prompt like *"screen these twelve
parcel coordinates for Section 404 exposure and tell me which need a wetland
delineation"* becomes one tool call returning structured rows. Field-level
descriptions in the output schema are written for an agent to read, and
`jurisdiction_basis` gives the agent the reasoning to quote back.

***

### Reliability

- Every request has a 45 s `AbortController` timeout and retries with
  exponential backoff on 408/425/429/5xx.
- Missing or empty `assets` is rejected by the input schema **before the run
  starts**, so nothing is consumed.
- Per-layer failures are recorded on the row, not just in the log, and a
  negative verdict is suppressed rather than asserted over missing data.
- If some assets resolve and others fail, the failures come back as rows
  carrying an `error`, a `null` flag and `screening_status: "failed"` — good
  rows are never discarded, and a failed site never reads as clear.
- The run fails outright when nothing can be answered at all, and then nothing
  is emitted and nothing is billed.
- The 2000-record page cap is handled by following `exceededTransferLimit` with
  `resultOffset` pagination.

***

### FAQ

**How do I find the nearest perennial stream to a property?**
Pass the property's latitude and longitude in `assets`. The row's
`nearest_perennial_dist_m` is the distance in metres to the nearest NHD FCode
46006 reach, and `nearest_flowline_name` / `reachcode` identify it.

**What is the difference between perennial, intermittent and ephemeral streams
in NHD?**
NHD encodes it in the FCode: 46006 perennial (flows year-round), 46003
intermittent (flows seasonally), 46007 ephemeral (flows only in direct response
to precipitation). This actor returns the distance to the nearest of each, and
only the first two can raise the review flag.

**Do I need a Section 404 permit for my project site?**
This actor gives you the screening inputs, not the legal determination. A `HIGH`
flag means a relatively permanent water is within 100 ft and a wetland
delineation plus an approved jurisdictional determination is the normal next
step. Only the US Army Corps of Engineers can make the actual call.

**Are ephemeral streams jurisdictional after Sackett v. EPA?**
Generally no — which is exactly why this actor reports ephemeral distances
separately and does not let them raise the review flag. The same applies to
ditches, washes and playas.

**Why did a desert site come back `LOW` when it is sitting in a wash?**
Because a wash (NHD FType 484) is an ephemeral feature and is generally not a
water of the United States after *Sackett*. The wash is still reported — see
`nearest_wash_playa_dist_m`, `nearest_waterbody_type` and
`nearest_waterbody_is_jurisdictional` — it just does not raise the flag.

**What does `mean_annual_flow_cfs` tell me?**
It is NHDPlus's modelled mean annual flow for that reach in cubic feet per
second, and it is the best openly available proxy for the *relatively permanent
flow* test. A reach carrying 100 cfs supports a very different argument from one
carrying 0.07 cfs. It is null when the nearest reach came from the non-network
layer, which does not carry NHDPlus attributes at all.

**Why is `section_404_review_flag` sometimes null?**
Because one of the NHD layers did not answer for that site, so a `LOW` or
`MEDIUM` would be asserting a negative that was never verified. Check
`layers_unavailable` and `section_404_flag_reason`, and re-run when the USGS
service is healthy.

**Which HUC12 watershed is my site in, and where does it drain?**
`huc12` and `huc12_name` give the containing subwatershed from the Watershed
Boundary Dataset; `tohuc` gives the HUC12 immediately downstream, and
`tohuc_is_terminal` tells you when the outlet leaves the HUC network entirely.
`huc8` and `huc10` are derived for the permit forms that ask for them.

**How do I batch-screen a portfolio of parcels for wetland risk?**
Put all of them in `assets` in one run — one row comes back per parcel,
including parcels with no nearby water. Sort by `section_404_review_flag` and
`nearest_swamp_marsh_dist_m`, and filter to `screening_status = "complete"`.

**Is the USGS NHDPlus HR data free to use?**
Yes. It is US Government public-domain data served without an API key. This
actor charges for the compute and the transform, and every row carries the exact
`source_url` used so any number can be re-derived.

***

### Pricing

Pay per result: **$12 per 1,000 results**. One result = one screened asset row.
Rows emitted for assets that failed validation or could not be screened carry an
`error` field and a null flag, so you can see exactly what was charged for. If
*nothing* could be screened, the run fails and no result events are charged.

***

### Related actors

Section 404 exposure is one layer of a site screen. These share the same
`[{lat, lon, label}]` input shape and join on `asset_label`:

| Actor | What it adds |
| --- | --- |
| [`fws-wetlands-proximity-screener`](https://apify.com/malonestar/fws-wetlands-proximity-screener) | USFWS **National Wetlands Inventory** polygons — the authoritative wetland mapping. NHD's Swamp/Marsh is a coarse proxy; NWI is what a delineation is scoped against. Run both. |
| [`epa-impaired-waters-303d-screener`](https://apify.com/malonestar/epa-impaired-waters-303d-screener) | Whether the receiving water you just identified is **CWA §303(d) impaired**, and for which pollutant — which drives TMDL and discharge-permit conditions. |
| [`epa-contaminated-site-screener`](https://apify.com/malonestar/epa-contaminated-site-screener) | Superfund, RCRA, USTs, NPDES and enforcement history for the ASTM E1527-21 recognised-environmental-conditions section of the same Phase I. |
| [`site-due-diligence-bundle`](https://apify.com/malonestar/site-due-diligence-bundle) | All twenty diligence layers for one coordinate in one call — contamination, wetlands, flood, seismic, habitat, historic, levee, karst and more — with a composite site score and a go / caution / no-go verdict. |

# Actor input Schema

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

Required. The list of sites to screen, in the standard \[{lat, lon, label}] shape. Use decimal degrees (lon is negative in the USA). label is optional free text - it is echoed on every output row so you can join results back to your parcel list. Example: \[{"lat":40.0143,"lon":-105.2829,"label":"Boulder CO parcel"}]. The four prefilled sites deliberately cover the range of outcomes: a creekside parcel 5 m from a perennial stream carrying 103 cfs (HIGH), a Louisiana site sitting inside an NHD swamp/marsh (HIGH, wetland-driven), an Arizona parcel inside a mapped desert wash (LOW - washes are reported but are not jurisdictional after Sackett), an upland Mojave parcel whose only nearby feature is an ephemeral reach (LOW), and an open-coast parcel at the mouth of Mobile Bay sitting on the Gulf of Mexico polygon (HIGH) - the coastal case that build 1.1.6 and earlier could not screen at all.

## `radiusMeters` (type: `integer`):

How far around each site to look for NHD flowlines, waterbodies and water areas. 1000 m covers a typical Phase-I ESA adjacent-property review; widen to 3000 m for utility-scale solar, BESS or data-center siting. Clamped to 50-8000 m.

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

Upper bound on the number of assets screened, and therefore on the number of dataset rows produced. One asset always produces exactly one row, including sites that turn out to be far from any mapped water. Clamped to 1-10000.

## `includeNonNetworkFlowlines` (type: `boolean`):

Also query NHDPlus HR layer 4 (NonNetworkNHDFlowline) for isolated ditches, canals and disconnected reaches near the site. Leave on for a conservative wetland-delineation scope; turn off to save one request per asset. Note that layer 4 carries no NHDPlus value-added attributes, so a nearest reach found there has no mean annual flow, stream order or drainage area.

## Actor input object example

```json
{
  "assets": [
    {
      "lat": 40.0143,
      "lon": -105.2829,
      "label": "Boulder CO - creekside redevelopment parcel"
    },
    {
      "lat": 29.99091,
      "lon": -89.93323,
      "label": "New Orleans East LA - swamp/marsh adjacent site"
    },
    {
      "lat": 33.42931,
      "lon": -111.98414,
      "label": "Tempe AZ - parcel inside a mapped desert wash"
    },
    {
      "lat": 35.2,
      "lon": -115.9,
      "label": "Mojave NP CA - upland solar reference site"
    },
    {
      "lat": 30.2481,
      "lon": -88.0783,
      "label": "Dauphin Island AL - Mobile Bay mouth coastal parcel"
    }
  ],
  "radiusMeters": 1000,
  "maxResults": 100,
  "includeNonNetworkFlowlines": true
}
```

# Actor output Schema

## `results` (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 = {
    "assets": [
        {
            "lat": 40.0143,
            "lon": -105.2829,
            "label": "Boulder CO - creekside redevelopment parcel"
        },
        {
            "lat": 29.99091,
            "lon": -89.93323,
            "label": "New Orleans East LA - swamp/marsh adjacent site"
        },
        {
            "lat": 33.42931,
            "lon": -111.98414,
            "label": "Tempe AZ - parcel inside a mapped desert wash"
        },
        {
            "lat": 35.2,
            "lon": -115.9,
            "label": "Mojave NP CA - upland solar reference site"
        },
        {
            "lat": 30.2481,
            "lon": -88.0783,
            "label": "Dauphin Island AL - Mobile Bay mouth coastal parcel"
        }
    ],
    "radiusMeters": 1000,
    "maxResults": 100,
    "includeNonNetworkFlowlines": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("malonestar/nhd-surface-water-404-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.0143,
            "lon": -105.2829,
            "label": "Boulder CO - creekside redevelopment parcel",
        },
        {
            "lat": 29.99091,
            "lon": -89.93323,
            "label": "New Orleans East LA - swamp/marsh adjacent site",
        },
        {
            "lat": 33.42931,
            "lon": -111.98414,
            "label": "Tempe AZ - parcel inside a mapped desert wash",
        },
        {
            "lat": 35.2,
            "lon": -115.9,
            "label": "Mojave NP CA - upland solar reference site",
        },
        {
            "lat": 30.2481,
            "lon": -88.0783,
            "label": "Dauphin Island AL - Mobile Bay mouth coastal parcel",
        },
    ],
    "radiusMeters": 1000,
    "maxResults": 100,
    "includeNonNetworkFlowlines": True,
}

# Run the Actor and wait for it to finish
run = client.actor("malonestar/nhd-surface-water-404-screener").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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.0143,
      "lon": -105.2829,
      "label": "Boulder CO - creekside redevelopment parcel"
    },
    {
      "lat": 29.99091,
      "lon": -89.93323,
      "label": "New Orleans East LA - swamp/marsh adjacent site"
    },
    {
      "lat": 33.42931,
      "lon": -111.98414,
      "label": "Tempe AZ - parcel inside a mapped desert wash"
    },
    {
      "lat": 35.2,
      "lon": -115.9,
      "label": "Mojave NP CA - upland solar reference site"
    },
    {
      "lat": 30.2481,
      "lon": -88.0783,
      "label": "Dauphin Island AL - Mobile Bay mouth coastal parcel"
    }
  ],
  "radiusMeters": 1000,
  "maxResults": 100,
  "includeNonNetworkFlowlines": true
}' |
apify call malonestar/nhd-surface-water-404-screener --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=malonestar/nhd-surface-water-404-screener",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/f6x48cbfTRbbVlSTh/builds/2NkGA24t2edeGhAZQ/openapi.json
