# USGS Streamflow Scraper (`automation-lab/usgs-river-gauge-levels`) Actor

Query official USGS gauges by site, state, or bounding box and export normalized streamflow and river-level observations for recurring water monitoring.

- **URL**: https://apify.com/automation-lab/usgs-river-gauge-levels.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Education, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.37 / 1,000 observation exporteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## USGS Streamflow Scraper

Export current **USGS streamflow** and river gauge observations from the official USGS Water Services API. Select known gauges, a US state, or a geographic bounding box and receive normalized discharge and gage-height rows ready for spreadsheets, GIS tools, dashboards, and scheduled monitoring.

The Actor uses a public, keyless USGS endpoint. It does not scrape visual pages, require an account, or use a proxy.

### What does USGS Streamflow Scraper do?

The Actor requests the latest instantaneous values available for your location filter and selected USGS parameter codes. It converts nested WaterML JSON into one flat row for each station, parameter, and observation.

Each result includes:

- official station ID and name;
- coordinates and hydrologic location fields;
- parameter code, name, and description;
- numeric value and source unit;
- observation timestamp;
- qualifier codes and normalized approval status;
- USGS station and API provenance links;
- retrieval time.

USGS no-data sentinel values are returned as `null`, not as valid measurements.

### Who is this Actor for?

- **Water operations teams** refreshing selected river-gauge readings.
- **Flood analysts** assembling current official observations for downstream models.
- **Anglers, paddlers, and recreation apps** checking discharge or gage height.
- **Researchers and journalists** exporting source-linked measurements.
- **GIS and data engineers** feeding scheduled USGS observations into pipelines.

This Actor provides measurements, not forecasts, alerts, or flood-stage decisions.

### Why use this USGS streamflow exporter?

USGS WaterML responses are authoritative but nested. The Actor supplies a stable, flat dataset and the same input contract for site, state, and bounded-area queries. It also handles transient official-host failures with bounded retries and an alternate official USGS hostname.

The default dataset works directly with Apify exports, API clients, webhooks, Zapier, Make, Google Sheets, and database integrations.

### Choose a location mode

Provide exactly one location selector:

1. `siteIds` for a stable list of known gauges;
2. `stateCode` for current observations in one US state; or
3. `boundingBox` for a GIS-style area query.

Combining location modes fails validation instead of silently broadening the request.

### Input parameters

| Field | Type | Default | Description |
|---|---|---:|---|
| `siteIds` | string array | — | 1–100 numeric USGS monitoring-location IDs. |
| `stateCode` | string | — | Two-letter US postal abbreviation such as `CA`. |
| `boundingBox` | number array | — | `[west, south, east, north]` in decimal degrees. |
| `parameterCodes` | string array | `00060`, `00065` | 1–10 five-digit USGS parameter codes. |
| `maxItems` | integer | `100` | Maximum rows saved, from 1 to 10,000. |

Common parameter codes:

- `00060` — discharge in cubic feet per second;
- `00065` — gage height in feet;
- `00010` — water temperature in degrees Celsius where available.

Availability varies by station. A requested parameter that a station does not measure produces no row.

### Get started

1. Open the Actor input page.
2. Keep the prefilled site `01646500`, or replace it with your gauges.
3. Keep discharge and gage height, or add supported parameter codes.
4. Set `maxItems` for your downstream workflow.
5. Click **Start**.
6. Open the dataset and export JSON, CSV, Excel, XML, or RSS.

Example input for one gauge:

```json
{
  "siteIds": ["01646500"],
  "parameterCodes": ["00060", "00065"],
  "maxItems": 10
}
```

Example state query:

```json
{
  "stateCode": "CA",
  "parameterCodes": ["00060", "00065"],
  "maxItems": 100
}
```

Example bounded-area query near Sacramento:

```json
{
  "boundingBox": [-121.65, 38.45, -121.25, 38.75],
  "parameterCodes": ["00060", "00065"],
  "maxItems": 50
}
```

### Output fields

| Field | Meaning |
|---|---|
| `stationId` | Official USGS monitoring-location ID. |
| `stationName` | Official station name. |
| `latitude`, `longitude` | Station coordinates. |
| `stateCode`, `countyCode` | Numeric source geography codes. |
| `hydrologicUnitCode` | USGS hydrologic unit code. |
| `siteTypeCode` | Source site type, for example `ST`. |
| `parameterCode` | Five-digit USGS parameter code. |
| `parameterName` | Human-readable parameter name. |
| `parameterDescription` | Detailed source description. |
| `value` | Numeric reading, or `null` for source no-data. |
| `unit` | Source unit code. |
| `observedAt` | Timestamp with station timezone offset. |
| `qualifiers` | USGS qualifier codes such as `P` or `A`. |
| `status` | `provisional`, `approved`, or `unknown`. |
| `methodId` | Source method ID where supplied. |
| `sourceUrl` | Public station page. |
| `requestUrl` | Official API request used. |
| `retrievedAt` | UTC normalization time. |

### Example result

Values change continuously. A current result has this shape:

```json
{
  "stationId": "01646500",
  "stationName": "POTOMAC RIVER NEAR WASH, DC LITTLE FALLS PUMP STA",
  "latitude": 38.94977778,
  "longitude": -77.12763889,
  "stateCode": "24",
  "countyCode": "24031",
  "hydrologicUnitCode": "020700081005",
  "siteTypeCode": "ST",
  "parameterCode": "00060",
  "parameterName": "Streamflow, ft³/s",
  "parameterDescription": "Discharge, cubic feet per second",
  "value": 1330,
  "unit": "ft3/s",
  "observedAt": "2026-09-19T09:50:00.000-04:00",
  "qualifiers": ["P"],
  "status": "provisional",
  "methodId": 69928,
  "sourceUrl": "https://waterdata.usgs.gov/monitoring-location/01646500/",
  "requestUrl": "https://nwis.waterservices.usgs.gov/nwis/iv/?format=json&sites=01646500",
  "retrievedAt": "2026-09-19T13:55:00.000Z"
}
```

### How much does it cost to export USGS river gauge observations?

Pricing is pay per event. A run has a **$0.0001 start fee** plus one `observation` event for each saved row. On the BRONZE tier, an observation is **$0.00728**.

BRONZE billing examples:

| Saved observations | Charged events |
|---:|---|
| 1 | 1 start + 1 observation |
| 10 | 1 start + 10 observations |
| 25 | 1 start + 25 observations |
| 100 | 1 start + 100 observations |

Multiply the active observation price by the saved count and add one start fee. Your active Apify pricing tier determines the exact price displayed in Console. Charges exclude platform-plan costs and may be affected by refunds, fraud, disputes, taxes, corrections, or clawbacks.

Rows rejected during normalization and empty results do not incur observation events.

### Schedule recurring river monitoring

Create an Apify schedule for hourly or daily runs. Prefer `siteIds` for a stable watchlist. Send completed datasets through a webhook or integration, then compare `value`, `observedAt`, and `status` with your previous run.

The Actor itself does not persist baselines or issue alerts. Your downstream system decides what change is meaningful.

### Export to spreadsheets and data pipelines

Use the dataset export menu for CSV or Excel. For automated ETL, read the default dataset API after the run succeeds. Stable station IDs and parameter codes make suitable compound keys; include `observedAt` when retaining a time series.

Do not interpret `retrievedAt` as the measurement time. Use `observedAt` for the actual observation.

### Run through the Apify API with cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~usgs-river-gauge-levels/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteIds":["01646500"],"parameterCodes":["00060","00065"],"maxItems":10}'
```

Keep tokens in environment variables or a secret manager. Never commit them.

### Run with JavaScript

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/usgs-river-gauge-levels').call({
  siteIds: ['01646500'],
  parameterCodes: ['00060', '00065'],
  maxItems: 10,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Run with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/usgs-river-gauge-levels').call(run_input={
    'siteIds': ['01646500'],
    'parameterCodes': ['00060', '00065'],
    'maxItems': 10,
})
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### Use with Apify MCP

Add the Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/usgs-river-gauge-levels"
```

#### Claude Desktop, Cursor, and VS Code MCP setup

Use this same remote-server JSON in Claude Desktop, Cursor, or VS Code's MCP configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/usgs-river-gauge-levels"
    }
  }
}
```

Example prompts:

- “Get the latest discharge and gage height for USGS station 01646500.”
- “Export up to 50 current USGS observations in this Sacramento bounding box.”
- “Fetch California streamflow observations for my spreadsheet pipeline.”

### Reliability and retry behavior

The Actor first calls `nwis.waterservices.usgs.gov`. Transient failures receive one bounded retry; the Actor then tries the alternate official `waterservices.usgs.gov` hostname. Each request has a 45-second timeout.

After all bounded attempts fail, the run fails visibly. It does not return a misleading empty success for an upstream outage.

### Limits and data freshness

- Results are the latest instantaneous values returned by USGS, not historical archives.
- `maxItems` limits saved rows; the upstream state or area response may contain more series.
- A gauge may not publish every requested parameter.
- USGS can mark data provisional and later revise it.
- Source coverage, cadence, station status, units, and methods vary.
- Large state queries depend on official service response size and availability.
- USGS has announced migration away from legacy Water Services in 2027; this Actor may require a future API migration.

### Legality and responsible use

The Actor retrieves public US government hydrologic observations. Follow USGS terms, notices, and attribution guidance. Preserve `sourceUrl`, `requestUrl`, qualifiers, units, and timestamps when republishing results.

Do not treat a raw gauge reading as an emergency warning, forecast, navigation guarantee, or safety instruction. Consult official local authorities and relevant National Weather Service products for hazardous conditions.

The Actor is independently operated and is not affiliated with or endorsed by the USGS.

### FAQ and troubleshooting

#### Why did I get “Provide exactly one location filter”?

Remove extra location fields. Keep only `siteIds`, `stateCode`, or `boundingBox`.

#### Why is the dataset empty?

The selected stations or area may not have a current value for the requested parameter codes. Confirm the codes and station activity on the linked USGS pages. An actual upstream failure causes a failed run instead of an empty success.

#### Why is `value` null?

USGS returned its no-data sentinel for that series. The Actor deliberately converts the sentinel to `null` so it cannot be mistaken for a physical reading.

#### Why is the status provisional?

A `P` qualifier means USGS may revise the measurement. An `A` qualifier is normalized as approved; unrecognized combinations remain `unknown` while the raw codes stay in `qualifiers`.

#### Can I request history?

Not in this release. It exports the latest instantaneous observation available per returned time series. Use a schedule and store successive datasets if you need a recurring snapshot workflow.

### Related automation-lab Actors

- [Weather.gov Active Alerts Tracker](https://apify.com/automation-lab/weather-gov-alerts-tracker) — pair observations with official active weather alerts.
- [NOAA Historical Weather Observations](https://apify.com/automation-lab/noaa-historical-weather-observations) — export bounded historical daily weather station data.
- [NOAA Buoy Data Scraper](https://apify.com/automation-lab/noaa-buoy-observations) — retrieve marine station observations.

### Support

Use the Actor issue tab with a sanitized input, run ID, expected result, and observed behavior. Do not include Apify tokens or other credentials.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/usgs-river-gauge-levels/changelog.md

# Actor input Schema

## `siteIds` (type: `array`):

One to 100 numeric USGS monitoring-location IDs. Use this for a stable gauge watchlist.

## `stateCode` (type: `string`):

Two-letter postal code, such as CA or TX. Do not combine with site IDs or a bounding box.

## `boundingBox` (type: `array`):

Four coordinates in USGS order: \[west longitude, south latitude, east longitude, north latitude]. Do not combine with another location filter.

## `parameterCodes` (type: `array`):

Five-digit parameter codes. Defaults to discharge (00060) and gage height (00065).

## `maxItems` (type: `integer`):

Maximum normalized station-and-parameter observations saved to the dataset.

## Actor input object example

```json
{
  "siteIds": [
    "01646500"
  ],
  "parameterCodes": [
    "00060",
    "00065"
  ],
  "maxItems": 20
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset containing all exported station-and-parameter observations.

# 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 = {
    "siteIds": [
        "01646500"
    ],
    "parameterCodes": [
        "00060",
        "00065"
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/usgs-river-gauge-levels").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 = {
    "siteIds": ["01646500"],
    "parameterCodes": [
        "00060",
        "00065",
    ],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/usgs-river-gauge-levels").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 '{
  "siteIds": [
    "01646500"
  ],
  "parameterCodes": [
    "00060",
    "00065"
  ],
  "maxItems": 20
}' |
apify call automation-lab/usgs-river-gauge-levels --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/usgs-river-gauge-levels"
        }
    }
}
```

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/AoaYfrAaO0cPEMDKi/builds/NZXvqwiisLuZoF4l6/openapi.json
