09/01/2026: Announcement: We at USGS Water Data for the Nation want your feedback! Tell us how we're doing by taking our quick survey, available through September 2026.
08/27/2026: Bug fix: nwis.get_discharge_peaks and nwis.get_record(service='peaks') discarded every peak whose date is only partly known. NWIS zero-fills the unknown part of a historical peak's date -- YYYY-MM-00 when the day is not known, YYYY-00-00 when the month is not either (the Bd and Bm peak_cd qualifiers) -- and neither parses as a date, so preformat_peaks_response coerced both to NaT and then dropped the row along with its discharge value. These are real peaks, and disproportionately a site's largest: site 14105700 lost 20 of its 167 peaks, among them an 1859 flood of 847,000 ft3/s, and 06934500 lost its 1844 peak of 700,000 ft3/s. Such a peak is now kept, with datetime left as NaT. The date is not completed into one NWIS does not have: a datetime64 column cannot hold a partial date, so any value there would assert a day the record does not contain. Behavior change: peaks queries return more rows than before, and datetime may now be NaT -- a caller selecting on the datetime index will not see those peaks and should filter on peak_dt instead. Behavior change: peak_dt is no longer removed from the returned frame. It is the only column that holds a censored peak's year, since the peaks response has no water_yr, and the only dependable way to tell an unknown day from a known one -- peak_cd does not always include the qualifier (22 of 24 censored dates across six sites tested). For peaks with a resolved timestamp alongside explicit year/month/day and a qualifier field, use waterdata.get_peaks().
08/26/2026: Bug fix: nwis.format_response(df, service='peaks') and nwis.preformat_peaks_response raised KeyError('peak_dt') on an empty peaks response instead of returning an empty frame. Both are public, and every other service already treated an empty result as a legitimate empty frame rather than an error (issue #171); the peaks branch was missed because it reformats the datetime column before the empty-frame check. Callers can now check df.empty rather than catching an exception. A non-empty frame with no peak_dt column is malformed rather than empty, and still raises.
08/25/2026: Removed dataretrieval.ogc.retry, which only re-exported private helpers. Deprecated dataretrieval.ogc.interruptions; import exceptions from dataretrieval or dataretrieval.interruptions instead. The old path will be removed in a future major release, no earlier than 2027-08-25.
08/20/2026: The state filter now accepts the five US territories. dataretrieval.codes.states held the 50 states and DC, so ngwmn.get_sites(state='Puerto Rico'), waterdata.get_monitoring_locations(state_name='Puerto Rico') via the unified state, and nwdc.get_wateruse(state='PR') were refused locally -- while all three services have the data (NGWMN returns 36 Puerto Rico monitoring locations, the Water Data monitoring-locations collection returns Puerto Rico sites, and legacy NWIS lists 1,148 stream sites for stateCd=PR). American Samoa, Guam, the Northern Mariana Islands, Puerto Rico and the US Virgin Islands are now in both code tables under their real ANSI/FIPS codes, so every encoding resolves: 'Puerto Rico', 'PR', '72' and 'US:72' all normalize alike. Behavior change: a territory that used to raise ValueError now produces a request. A value the table does not hold still fails fast.
08/20/2026: Argument checks now share one vocabulary, and every rejection explains how to correct the call. dataretrieval._validation owns the message shape for each check that recurs across the adapters -- a value outside a closed vocabulary (require_one_of), a missing argument (require_argument, require_together), a query with no filter at all (require_any_of), and arguments that conflict (require_exactly_one, reject_together) -- so a new check cannot invent its own phrasing, which is how get_reference_table came to tell callers who passed a bad collection that their code service was invalid. Each check takes the caller's own name for the parameter and a remedy for the action it cannot derive, and every check raises ValueError -- one class for a bad argument value. Behavior change: the text of those rejections moves with them -- "Unrecognized service: 'x'. get_record serves …" is now "Invalid service: 'x'. Valid options are: …", and the major-filter and bounding-box rejections in query_waterdata / query_waterservices are rendered in the shared form. Behavior change: the deprecated nwis query entry points (query_waterdata, query_waterservices, get_record) now respond to a missing major filter, an incomplete bounding box, or an unknown service with ValueError rather than their historic TypeError -- TypeError remains for a mistyped argument, such as a non-string sites. Code catching TypeError there, or matching on the old strings, must update. Bug fix: a None passed as a major filter (query_waterservices(service='dv', sites=None)) counted as a filter and reached the service as an empty sites=; None now means not supplied, and the call is refused with the filters that would have been accepted. Bug fix: four messages told callers to do something that raised again or named a parameter their getter does not accept -- nldi.get_features(comid=…, feature_id=…) said to supply the missing half of the feature pair, and the corrected call then failed on the conflict with comid; nwdc responded to state=[] by saying exactly one of state, county or huc must be given, when exactly one was; codes.states.apply_state offered NGWMN callers a state_name / state_code parameter no NGWMN getter accepts; and BaseMetadata directed NGWMN and NWDC callers to a Water Data getter. Bug fix: nldi.get_features(navigation_mode=…) without a data_source wrote None into the URL path and returned an empty frame from a 200; it now raises and names the source to pass. Behavior change: waterdata.get_nearest_continuous appends time and monitoring_location_id to an explicit properties list rather than using it verbatim -- omitting either collapsed every monitoring location into one row per target, a wrong result rather than an error -- so a caller who passed properties=['time', 'value'] now gets a third column. Behavior change: nwis.query_waterdata serves 'peaks' only; the 'ratings' URL it used to build was never an NwisWeb program and responded with an HTML error page. nwis.get_record(service='ratings') is unaffected -- it routes to get_ratings, which is served from a different endpoint. New: waterdata.get_cql(..., max_rows=N) caps the total rows a CQL query returns.
08/13/2026: Warning categories now match their meaning. Two advisories about upstream data were emitted as DeprecationWarning and as an uncategorized warnings.warn respectively; both are now DataCurrencyWarning (a UserWarning subclass, exported as dataretrieval.DataCurrencyWarning). Behavior change: WQP's legacy-WQX notice moves from DeprecationWarning to DataCurrencyWarning. Because legacy=True is the default on every WQP getter and the notice is unconditional, a downstream project running -W error::DeprecationWarning previously could not call any WQP getter with default arguments; it now can. The cost is visibility — DeprecationWarning is silent by default outside __main__, so this notice will now print to stderr for library and notebook callers who never saw it. Suppress it with warnings.filterwarnings("ignore", category=dataretrieval.DataCurrencyWarning), or set legacy=False where a WQX3.0 profile exists. The NWIS qw-endpoint retirement notice gains the same category (it previously had none, emitted as a bare UserWarning that deprecation filters ignored). DeprecationWarning now means only that a name in this package is being removed, always with a replacement and, where published, a removal date; those horizons are declared once in dataretrieval._deprecation.REMOVALS rather than declared at each call site.
08/11/2026: Settings resolve through a layered chain instead of the environment alone, and a configuration profile is a named set of settings for one adapter. The new dataretrieval.configuration module resolves every setting in one order, highest first: a configuration passed to an active dataretrieval.configure(...) block, a profile that block selected, the setting's API_USGS_* environment variable, the adapter's [<adapter>] table in ~/.dataretrieval/config.toml (or DATARETRIEVAL_CONFIG), the file's top-level keys, the adapter's own built-in preference, then the package default. Precedence applies per setting, so a file that sets only concurrency leaves an environment API_USGS_PAT in effect, and a [ngwmn] table still inherits every top-level key it does not name. configure() takes configuration objects positionally, at most one per adapter and nothing else: configure(Configuration(api_key=vault.read("usgs/pat")), WaterdataConfiguration.load("bulk"), NgwmnConfiguration(concurrency=4)). The adapter a configuration targets is a property of its class, so a caller never restates it, and each adapter owns its class in the module that reads those settings (waterdata.WaterdataConfiguration, ngwmn.NgwmnConfiguration, nwdc.NwdcConfiguration, wqp.WqpConfiguration, nldi.NldiConfiguration, streamstats.StreamstatsConfiguration) — an adapter accepts only the settings it reads, so [streamstats] parallel_chunks = 8 is an error rather than a line that does nothing. The block is delivered through a ContextVar, so a credential set inside it cannot leak across threads or asyncio tasks, which is what makes it safe for a server or notebook handling several users' keys and is the thing assigning to os.environ could never do (issue #352). The file gains named profiles beside each adapter's default profile: [waterdata] is always in effect, [waterdata.bulk] only when a caller selects it with WaterdataConfiguration.load("bulk"), and a selected profile still inherits the default profile and the package-wide keys per setting. A profile named in code outranks the setting's environment variable — the one place the ladder inverts the environment-above-file rule, because a stale shell export overriding a deliberate selection would look like a bug. An adapter's configuration may also include a base_url, which redirects that adapter's requests for the duration of the block — a staging instance, a mirror, a recording proxy — and no other adapter's; for Water Data one value redirects the OGC collections, the Samples database, the statistics service and the STAC catalog together. It is settable in a configure() block only: a base_url key in the file and an exported API_USGS_BASE_URL each raise rather than being read, since a redirect a config file or a shell profile can set is one no reader of the script can see. The API key does not follow a redirect — it is scoped to the single host that accepts it — and is deliberately not per-adapter: it authenticates to the gateway fronting a host, and Water Data and NGWMN share that host, one key, and one hourly quota. dataretrieval.show_configuration() reports each setting's effective value and where it came from, naming the profile behind each value (configure() block [waterdata.bulk] rather than a bare block), listing the profiles a file defines whether or not this run selected any, and naming any adapter this process has not imported rather than omitting it — without ever printing the key. One parser per setting owns its grammar, so a value means the same thing whichever source wrote it. Breaking change: RetryPolicy.from_env() is now RetryPolicy.from_configuration() and resolves through the whole chain rather than the environment alone. Behavior change: a credential-shaped keyword passed to a getter's **kwargs query passthrough — Water Data's **queryables and every WQP getter's search filters — now raises TypeError naming configure(Configuration(api_key=...)) instead of putting a secret in a URL that clients, proxies and logs retain. The names refused are api_key=, token=, x_api_key=, password=, auth=, pat= and similar names; a filter the server defines is unaffected. Bug fix: API_USGS_STALL_TIMEOUT was read straight from os.environ, so it could not be set by a configure() block or the config file and never appeared in show_configuration(); it now resolves through the chain like every other setting. Rationale in ADRs 0009, 0010 and 0011; terms in CONTEXT.md.
08/11/2026: dataretrieval.wateruse is now dataretrieval.nwdc. Every other adapter is named for the service it retrieves from — ngwmn, nldi, wqp, streamstats, nwis — and this one was named for one subset of what its service offers. The National Water Availability Assessment Data Companion serves ten modeled datasets; the water-use models are five of them, the rest being hydrologic, atmospheric-forcing, and assessment outputs (GET https://api.water.usgs.gov/nwaa-data/models). Deprecation: dataretrieval.wateruse still works and re-exports dataretrieval.nwdc unchanged, emitting a DeprecationWarning on import; it will be removed on or after 2027-08-11. The alias forwards rather than copies, so wateruse.get_wateruse is nwdc.get_wateruse — monkeypatching or identity comparison through either name behaves the same. import dataretrieval emits no warning: the package imports nwdc directly, so only code naming wateruse itself sees the warning. Function and constant names are unchanged (get_wateruse, MODELS, WATERUSE_URL, DEFAULT_CONCURRENT_REQUESTS). Terms are defined in CONTEXT.md.
08/09/2026: waterdata.get_cql takes collection rather than service. OGC API - Features (17-069r4) normatively names this value the collectionId: Requirement 20 fixes the path template /collections/{collectionId}/items, and Requirement 18 defines collectionId as each id in the collections response -- which is how the package builds the URL, and what the live API returns. Service names the API itself (Water Data, NGWMN). Deprecation: service= still works and resolves to collection, with a DeprecationWarning; it will be removed on or after 2027-08-09. Positional callers (get_cql("daily", cql)) are unaffected. The WATERDATA_SERVICES type alias is now WATERDATA_COLLECTIONS, with WATERDATA_SERVICES retained as a permanent alias for the same object. Terms are defined in CONTEXT.md.
08/09/2026: Every retrieval path now runs through one executor. waterdata.get_cql (via the OGC fetch_ogc_request) and waterdata.get_stats_por / get_stats_date_range (via the Statistics page walk) previously bypassed dataretrieval.transport.fanout.FanOut through a private sync bridge, which meant they were the only getters in the package with no retry: a mid-page-walk 429 or 503 failed the whole call while every typed getter and Water Use retried through it. Both now run as a one-item fan-out and the 25-line transport/sync.py is gone. Behavior change: those three getters now retry transient failures (API_USGS_RETRIES, default 4) and, when the retries are exhausted, raise the resumable ServiceInterrupted / QuotaExhausted rather than ServiceUnavailable / RateLimited / NetworkError — all remain DataRetrievalError, so broad handlers are unaffected, but narrow handlers around those calls must widen, and .call.resume() is now available on the interruption. A failure that retrying cannot fix (bad scheme, a hostname that does not resolve) is still raised as NetworkError immediately. The progress line moved with it: FanOut.resume() opens the reporter it ticks into, so a driver can no longer run the shared executor and print nothing, and a .call.resume() invoked long after the interruption now reports progress instead of printing nothing. Internal reorganization with no public effect: the WQX3 / legacy-WQP CSV datetime shaping moved out of dataretrieval.utils (whose docstring reserves it for non-service-specific shaping) into the dataretrieval._wqx leaf; the five Water Data endpoint URLs are declared once in dataretrieval.waterdata.endpoints instead of being derived in three modules; the OGC queryables document is parsed by dataretrieval.ogc.schema so every OGC adapter can return the table, with waterdata.get_queryables unchanged as its documented wrapper; and ogc/engine.py imports each symbol from the module that defines it.
08/09/2026: Internal structure cleanup, no public API change. Validating a server-supplied next-page link is now one policy in dataretrieval.transport.links instead of three divergent copies (the OGC engine, the ratings STAC walk, and Water Use). Two of those copies were fixed by the merge: the OGC page walk now resolves a relative next href against the page it came from (it previously returned the unresolved reference as the pagination cursor) and refuses an unparseable one rather than following it unchecked. Cross-host refusal, credential stripping, and Water Use's host-alias rewrite are unchanged, as is the error type each walk raises. parse_retry_after moved to dataretrieval.exceptions, next to the DataRetrievalError.retry_after field it exists to produce. The one-shot HTTP query path (query, to_str, and their helpers) moved out of dataretrieval.utils into the private dataretrieval._querying; dataretrieval.utils.query and dataretrieval.utils.to_str remain the documented public paths, as Ambient and BaseMetadata already do. waterdata profile validation moved next to the tables it validates in waterdata.types, and nwis.get_dv/get_iv now share one body.
08/06/2026: Fan-out execution is now shared across services. Chunking is how a query is divided structurally (a Water Data/NGWMN URL over the byte limit); fan-out is how the pieces are distributed operationally. Only the first is protocol-specific, so the executor moved to dataretrieval.transport.fanout (FanOut, over a three-member FanOutPlan protocol) while chunk planning stays in dataretrieval.ogc. Water Use no longer re-implements the fan-out gather and inherits resume, progress reporting, and API_USGS_CONCURRENT: a multi-location pull interrupted by a rate limit now raises a resumable interruption whose .call.resume() re-issues only the locations that did not finish, instead of discarding every completed one. The interruption taxonomy moved to the dataretrieval.interruptions leaf and its base class is now FanOutInterrupted; ChunkInterrupted is a permanent alias of the same class, so except ChunkInterrupted keeps working. Breaking change: a Water Use fan-out interrupted by a 5xx, 429, or recoverable connection failure now raises ServiceInterrupted/QuotaExhausted rather than ServiceUnavailable/RateLimited/NetworkError — all remain DataRetrievalError, so broad handlers are unaffected, but narrow handlers around a Water Use call must widen. Breaking change: wateruse.MAX_CONCURRENT_REQUESTS is removed; set API_USGS_CONCURRENT (which now outranks any service default) or read wateruse.DEFAULT_CONCURRENT_REQUESTS.
08/03/2026: Split the Water Data implementation into focused time-series, metadata, measurements, reference, samples, and CQL collection-family modules behind the unchanged waterdata.api facade. Active service modules now declare explicit exports; public Water Data imports, signatures, function identities, deprecations, and return contracts are protected by executable contract snapshots. OGC ambient context and schema/queryables execution are separated from request construction, adapter-to-adapter imports are prohibited by architecture tests, and service-specific output shapes are documented rather than forced into one model.
08/02/2026: Added an internal API-neutral transport layer for guarded HTTP clients, host-scoped authentication, cursor pagination, bounded retry, response aggregation, progress, and sync-over-async dispatch. Water Use and the non-OGC Statistics API now consume transport directly instead of private OGC execution helpers; WQP, NLDI, and StreamStats opt into bounded transient retry while deprecated NWIS behavior remains unchanged. OGC retains CQL2, request construction, feature shaping, chunk planning, resumable calls, and interruption types, with compatibility imports at previous private paths. Failed pagination and fan-out still raise rather than returning partial data, and no public signatures or return shapes changed.
08/02/2026: Phase 1 OGC boundary stabilization: the dataretrieval.ogc package now exposes a deliberate, small facade (OgcDialect, prepare_request_args, get_ogc_data, fetch_ogc_request) that service adapters (NGWMN, Water Data's generic wrapper) import. Internal request-construction helpers moved to a new ogc.requests module; the dialect type and endpoint constants are defined in the leaf ogc.policy module. ogc.shaping no longer depends on ogc.engine at all, and the complete runtime OGC import graph is now acyclic. _default_headers now accepts a target URL and adds X-Api-Key only for api.waterdata.usgs.gov; shared sync and async clients also strip the key before following any cross-host redirect, including external rating-asset downloads. waterdata.utils no longer bulk-re-exports private OGC symbols, and consumers import implementation helpers from their canonical modules. No public API, return-value, or deprecation changes.
08/02/2026: Fixed source-distribution and wheel package discovery so the dataretrieval.ogc and dataretrieval.waterdata subpackages are included in installed artifacts. CI now builds and installs the wheel outside the source checkout before importing the core service modules. Added an architecture baseline, initial decision records, and executable dependency-direction checks for the existing modular-monolith boundaries.
06/23/2026: Breaking change (1.2.0): the minimum supported Python is now 3.10 (requires-python = ">=3.10"). 3.9 support was already effectively broken — the waterdata module's dependencies (anyio, the test stack) require 3.10+, and the waterdata test modules already skipped on <3.10. anyio is now declared as a direct dependency (it is imported directly by waterdata), and the CI/ruff/mypy targets move to 3.10. Also fully removed the deprecated variable_info metadata property: the NWIS_Metadata override only warned and returned None (it relied on the defunct get_pmcodes), and the BaseMetadata abstract is gone too since nothing implemented it — accessing .variable_info now raises AttributeError. site_info is unaffected.
06/23/2026: Breaking change (1.2.0): removed the nadp module and the deprecated samples module ahead of the 1.2.0 release. nadp was deprecated on 05/01/2026 — NADP is not a USGS data source, so retrieve NADP data directly from https://nadp.slh.wisc.edu/. The samples.get_usgs_samples shim (a deprecated forward to the modern getter) is gone; use waterdata.get_samples() instead. import dataretrieval.nadp / import dataretrieval.samples now raise ModuleNotFoundError.
06/03/2026: The request-error hierarchy is now unified. Every module (nwis, wqp, nldi, waterdata, nadp, streamstats) raises a subclass of dataretrieval.DataRetrievalError on a failed request, so a single except dataretrieval.DataRetrievalError spans them all. An HTTP error status is raised as an HTTPError with .status_code (inspect it to branch on a specific code); the retryable 429/5xx subset is TransientError (RateLimited / ServiceUnavailable, with .retry_after); and a request too large to satisfy is a RequestTooLarge (URLTooLong for an over-long single request, Unchunkable when the Water Data chunker cannot split a call small enough). Connection-level failures (timeouts, DNS, refused connections) are wrapped as a NetworkError, with the underlying httpx exception on __cause__. Every DataRetrievalError also exposes .status_code (None when there is no HTTP status), .retry_after, and .retryable, so a single except dataretrieval.DataRetrievalError as e clause can branch on the status or retry transient failures without knowing the concrete subclass. Breaking change: these exceptions no longer multiply-inherit a built-in — code that caught request failures with except ValueError or except RuntimeError should switch to except dataretrieval.DataRetrievalError (or a specific subclass). A no-data result is not an error: the modern getters (waterdata, wqp, nldi) return an empty DataFrame when nothing matches. Only the deprecated nwis (waterservices) path still raises NoSitesError on no data.
05/17/2026: The OGC waterdata getters (get_daily, get_continuous, get_field_measurements, and the rest of the multi-value-capable functions) now automatically chunk requests whose URLs would otherwise exceed the server's ~8 KB byte limit.
05/16/2026: Fixed undetected truncation in the paginated waterdata request loops (_walk_pages and get_stats_data). Mid-pagination failures (HTTP 429, 5xx, network error) were previously caught and ignored — pagination would stop and the function would return whatever rows it had collected, leaving callers with truncated DataFrames they had no way to detect. The loops now status-check every page like the initial request and raise RuntimeError on any failure, with the upstream exception chained as __cause__ and a short list of recovery actions (wait and retry, reduce the request, or obtain an API token) in the message. Behavior change: callers that previously consumed partial DataFrames on transient upstream failures will now see an exception; retry the call (possibly with a smaller limit or narrower query).
05/07/2026: Bumped the declared minimum Python version from 3.8 to 3.9 (pyproject.toml's requires-python and the ruff target). This makes the manifest match what was already tested — CI's matrix has long covered only 3.9, 3.13, and 3.14, the waterdata test module already skipped itself on Python < 3.10, and several modules already use 3.9-only stdlib (e.g. zoneinfo). Users on 3.8 will no longer be able to install the package; please upgrade.
05/07/2026: waterdata.get_samples() and wqp.get_results() now append a derived <prefix>DateTime UTC column for every Date/Time/TimeZone triplet in the response (e.g. Activity_StartDate + Activity_StartTime + Activity_StartTimeZone → Activity_StartDateTime). Both the WQX3 (<X>Date/<X>Time/<X>TimeZone) and legacy WQP (<X>Date/<X>Time/Time/<X>Time/TimeZoneCode) shapes are recognized; abbreviations like EST/EDT/CST/PST resolve to a UTC Timestamp, unknown codes resolve to NaT, and the original triplet columns are preserved. Returned rows are also now sorted by Activity_StartDateTime (or the legacy ActivityStartDateTime) — the underlying APIs return rows in an unstable order. Matches R's create_dateTime and end-of-pipeline sort. Closes #266.
05/06/2026: Each remaining active function in dataretrieval.nwis now emits a per-function DeprecationWarning naming the waterdata replacement to migrate to (visible the first time users call each getter). The nwis module is scheduled for removal on or after 2027-05-06.
05/06/2026: Added waterdata.get_ratings(...) — wraps the new Water Data STAC catalog (api.waterdata.usgs.gov/stac/v0/search) for USGS stage-discharge rating curves. Returns parsed exsa / base / corr rating tables as a dict of DataFrames keyed by feature ID, or just the list of available STAC features when download_and_parse=False. Matches R's read_waterdata_ratings.
05/06/2026: Added waterdata.get_field_measurements_metadata(...) — wraps the OGC field-measurements-metadata collection. Returns one row per (location, parameter) field-measurement series describing its period of record, units, etc., without the underlying observations. Discrete-measurement analogue to get_time_series_metadata. Matches R's read_waterdata_field_meta.
05/06/2026: Added waterdata.get_peaks(...) — wraps the new OGC peaks collection, returning the annual peak streamflow / stage record for a monitoring location (one row per water year, per parameter). Standard input to flood-frequency analysis. Supports calendar/water-year filters and the usual location/parameter/CQL options shared with the other OGC getters.
05/05/2026: Added waterdata.get_combined_metadata(...) — wraps the Water Data API's combined-metadata collection, which joins the monitoring-locations catalog with the time-series-metadata catalog and returns one row per (location, parameter, statistic) inventory entry. This is the most flexible inventory endpoint in the API: any location attribute (state, HUC, site type, drainage area, well-construction depth, …) can be combined with any time-series attribute (parameter code, statistic, data type, period of record, …) in a single query. Matches R's read_waterdata_combined_meta.
05/05/2026: Added waterdata.get_samples_summary(monitoringLocationIdentifier=...) — wraps the Samples database /summary/{id} endpoint, returning per-characteristic result and activity counts plus first / most recent activity dates for a single monitoring location. Useful for taking inventory of available discrete-sample data before pulling observations with get_samples.
05/01/2026: The nadp module is now deprecated. Calling any of get_annual_MDN_map, get_annual_NTN_map, or get_zip will emit a DeprecationWarning. The module is scheduled for removal on or after 2026-11-01. NADP is not a USGS data source; users should retrieve NADP data directly from https://nadp.slh.wisc.edu/.
04/23/2026: Added waterdata.get_nearest_continuous(targets, ...) — for each of N target timestamps, fetches the single continuous observation closest to that timestamp in one HTTP round-trip (auto-chunked when the resulting CQL filter is long, via the facility added in #238). The helper is designed for workflows that pair many discrete-measurement timestamps with surrounding instantaneous data, which the OGC time parameter can't express since it only accepts one instant or one interval per request. Ties at window midpoints are resolved per a configurable on_tie ∈ {"first", "last", "mean"}; the default window="PT7M30S" matches a 15-minute continuous gauge.
04/22/2026: Highlights since the v1.1.0 release (2025-11-26), which shipped the waterdata module:
- Added
get_channelfor channel-measurement data (#218) andget_stats_por/get_stats_date_rangefor period-of-record and daily statistics (#207). - Added
get_reference_table(and made it considerably simpler and faster in #209), then extended it to accept arbitrary collections-API query parameters (#214). - Removed the deprecated
waterwatchmodule (#228) and several defunct NWIS stubs (#222, #225), and addedpy.typedsodataretrievalships type information to downstream users (#186). - Now supports
pandas3.x (#221). - The OGC
waterdatagetters (get_continuous,get_daily,get_field_measurements, and the six others built on the same OGC collections) now acceptfilterandfilter_langkwargs that are passed through to the service's CQL filter parameter. This enables advanced server-side filtering that isn't expressible via the other kwargs — most commonly, OR'ing multiple time ranges into a single request. A long expression made up of a top-levelORchain is automatically split into multiple requests that each fit under the server's URI length limit, and the results are concatenated.
12/04/2025: The get_continuous() function was added to the waterdata module, which provides access to measurements collected via automated sensors at a high frequency (often 15 minute intervals) at a monitoring location. This is an early version of the continuous endpoint and should be used with caution as the API team improves its performance. In the future, we anticipate the addition of an endpoint(s) specifically for handling large data requests, so power users may want to delay extensive development using the new continuous endpoint.
11/24/2025: dataretrieval has a new module, waterdata, which gives users access to USGS's modernized Water Data APIs. The Water Data API endpoints include daily values, instantaneous values, field measurements (modernized groundwater levels service), time series metadata, and discrete water quality data from the Samples database. Though there will be a period of overlap, the functions within waterdata will eventually replace the nwis module, which currently provides access to the legacy NWIS Water Services. More example workflows and functions coming soon. Check help(waterdata) for more information.
09/03/2024: The groundwater levels service has switched endpoints, and dataretrieval was updated accordingly in v1.0.10. Older versions using the discontinued endpoint will return 503 errors for nwis.get_gwlevels or the service='gwlevels' argument. Visit Water Data For the Nation for more information.
03/01/2024: USGS data availability and format have changed on Water Quality Portal (WQP). Since March 2024, data obtained from WQP legacy profiles will not include new USGS data or recent updates to existing data. All USGS data (up to and beyond March 2024) are available using the new WQP beta services. You can access the beta services by setting legacy=False in the functions in the wqp module.
To view the status of changes in data availability and code functionality, visit: https://doi-usgs.github.io/dataRetrieval/articles/Status.html