This guide shows how to use the Western Water Datahub
EDR API with plain HTTP requests. It does not assume R, Python,
JavaScript, or any other client library. If you can open a URL in a
browser, use curl, or send an HTTP GET request from another
tool, you can use these patterns.
Use the Swagger/OpenAPI page as the endpoint reference. Use this page when you want to understand which URL to call, which query parameters matter, and what kind of JSON to expect back.
The examples below were checked against live WWDH metadata on June 4, 2026. Re-run the discovery requests before important work because collections and upstream source behavior can change.
The base URL is:
https://api.wwdh.internetofwater.app
Most examples use f=json because WWDH is a pygeoapi
service and serves collection metadata, GeoJSON, and CoverageJSON
through JSON responses.
GET https://api.wwdh.internetofwater.app/collections?f=json
You can often request a browser-readable HTML page with
f=html:
GET https://api.wwdh.internetofwater.app/collections?f=html
For command-line use, quote URLs that contain ?,
&, parentheses, or spaces:
Useful f values:
f value |
Use | Common response |
|---|---|---|
json |
Programmatic access | JSON, GeoJSON, or CoverageJSON |
html |
Browser inspection | HTML page generated by pygeoapi |
csv |
Some feature/location endpoints | CSV table |
jsonld |
Linked-data representation when advertised | JSON-LD |
For EDR data queries, prefer f=json. The body will
usually identify itself as either GeoJSON
("type": "FeatureCollection") or CoverageJSON
("type": "Coverage" or
"type": "CoverageCollection").
Start by asking what the service provides.
GET /?f=json HTTP/1.1
Host: api.wwdh.internetofwater.app
Accept: application/json
Equivalent full URL:
https://api.wwdh.internetofwater.app/?f=json
Then list collections:
GET /collections?f=json HTTP/1.1
Host: api.wwdh.internetofwater.app
Accept: application/json
Equivalent curl:
The response has a top-level collections array. Each
collection has an id, title,
extent, links, and sometimes a
data_queries object.
The same document may also have a top-level
parameterGroups array. Use parameterGroups
when you want to translate a water-data concept across collections. For
example, the Lake/Reservoir Storage group currently maps
USBR RISE parameter 3 to USACE storage parameters such as
Flood Storage and Conservation Storage.
As of June 4, 2026, examples include:
| Collection id | What it is | Advertised data queries |
|---|---|---|
rise-edr |
USBR RISE reservoir telemetry | locations, cube, area,
items |
snotel-edr |
USDA SNOTEL station data | locations, cube, area,
items |
awdb-forecasts-edr |
USDA AWDB forecasts | locations, cube, area,
items |
usace-edr |
USACE Access2Water API | locations, cube, area,
items |
usgs-prism |
PRISM monthly climate grids | position, cube |
snotel-huc06-means |
HUC6 SWE summary features | Feature /items; no EDR data queries advertised |
Inspect one collection before asking for data:
Look for:
parameter_names: data variables you can request with
parameter-name.parameterGroups: cross-collection concept groups, when
present.data_queries: supported EDR query types for that
collection.links: HTML pages, JSON pages, /items,
/queryables, and source documentation links.extent: the rough spatial and temporal coverage.These are the routes you will use most often.
| Route | Purpose | Typical response |
|---|---|---|
/ |
Landing page | JSON or HTML links |
/conformance |
OGC conformance classes | JSON |
/collections |
List collections | JSON collection list |
/collections/{collectionId} |
Collection metadata | JSON collection document |
/collections/{collectionId}/queryables |
Filterable feature properties | JSON Schema |
/collections/{collectionId}/items |
Feature collection | GeoJSON, HTML, CSV |
/collections/{collectionId}/items/{itemId} |
One feature | GeoJSON feature |
/collections/{collectionId}/locations |
Station/location index | GeoJSON, HTML, CSV |
/collections/{collectionId}/locations/{locId} |
Data for one known location | CoverageJSON |
/collections/{collectionId}/position |
Data at one point | CoverageJSON |
/collections/{collectionId}/cube |
Data inside a bounding box | CoverageJSON |
/collections/{collectionId}/area |
Data inside a polygon | CoverageJSON |
/collections/{collectionId}/radius |
Data within radius of a point | CoverageJSON when implemented |
/collections/{collectionId}/trajectory |
Data along a line | CoverageJSON when implemented |
/collections/{collectionId}/corridor |
Data along a line with width | CoverageJSON when implemented |
Do not assume every collection supports every route. The collection document tells you what is advertised. A route that is valid for one collection may return 404 or 500 for another.
| Parameter | Used by | Meaning | Example |
|---|---|---|---|
f |
Almost all routes | Response format | f=json |
bbox |
locations, items, cube |
Bounding box as minx,miny,maxx,maxy |
bbox=-107,37,-105,40 |
datetime |
EDR data queries, some feature queries | ISO-8601 instant or interval | datetime=2024-02-01/2024-04-30 |
parameter-name |
EDR data queries | Comma-separated data variable ids | parameter-name=WTEQ |
coords |
position, area, trajectory,
corridor, radius |
WKT geometry | coords=POINT(-112.4%2036.9) |
limit |
locations, items, some providers |
Feature count limit | limit=10 |
crs |
Some EDR routes | Output coordinate reference system | Provider-specific URI |
z |
3D/profile-capable data | Vertical coordinate | z=0 |
within |
radius |
Radius distance | within=25 |
within-units |
radius |
Radius units | within-units=km |
corridor-width |
corridor |
Width around line | corridor-width=10 |
width-units |
corridor |
Corridor width units | width-units=km |
{queryable} |
items |
Feature property filter advertised by /queryables |
provider=SPL |
Coordinates are longitude, latitude. A bbox is always:
min_lon,min_lat,max_lon,max_lat
Multiple parameters are comma-separated:
parameter-name=ppt,tmx
Some parameter ids contain spaces. Encode them in a hand-built URL:
parameter-name=Flood%20Storage
or let curl encode them:
curl -sS -G 'https://api.wwdh.internetofwater.app/collections/usace-edr/locations/157145' \
--data-urlencode 'f=json' \
--data-urlencode 'datetime=2024-01-01/2024-12-31' \
--data-urlencode 'parameter-name=Flood Storage'Intervals use a slash:
datetime=2024-01-01/2024-03-31
Open intervals may be supported by some providers:
datetime=2024-01-01/..
HTTP URLs cannot contain every character literally. Encode reserved characters in query values.
For WKT point coordinates, encode the space between lon and lat as
%20:
coords=POINT(-112.4%2036.9)
A polygon must be URL-encoded if it contains spaces or parentheses. This readable WKT:
POLYGON((-106 39,-105.8 39,-105.8 39.2,-106 39.2,-106 39))
becomes a query value like:
coords=POLYGON((-106%2039,-105.8%2039,-105.8%2039.2,-106%2039.2,-106%2039))
Most HTTP clients can encode query parameters for you. If you build
URLs by hand, be especially careful with spaces, #,
&, +, and literal slashes inside path
ids.
Collection metadata is where EDR data variables live.
In the response, inspect parameter_names.
Example usgs-prism parameter ids:
| Parameter id | Meaning | Unit |
|---|---|---|
ppt |
Mean monthly precipitation | mm/month |
tmn |
Minimum monthly temperature | deg C |
tmx |
Maximum monthly temperature | deg C |
Example snotel-edr parameter ids:
| Parameter id | Meaning | Unit |
|---|---|---|
WTEQ |
Snow water equivalent | in |
SNWD |
Snow depth | in |
PREC |
Precipitation accumulation | in |
TAVG |
Average air temperature | deg F |
Example reservoir/stage parameter ids:
| Collection | Parameter id | Meaning | Unit |
|---|---|---|---|
rise-edr |
3 |
Daily lake/reservoir storage | acre-ft |
rise-edr |
1830 |
Lake/reservoir release - total | cfs |
usace-edr |
Flood Storage |
Flood storage | acre-ft |
usace-edr |
Conservation Storage |
Conservation storage | acre-ft |
usace-edr |
Outflow |
Outflow | cfs |
resviz-edr |
raw |
Daily lake/reservoir storage | provider-defined |
Use the exact parameter_names keys in the URL. Labels
are for humans; keys are for requests.
For cross-collection work, use the top-level
parameterGroups block from /collections?f=json
to find conceptually equivalent parameters. For example:
curl -sS 'https://api.wwdh.internetofwater.app/collections?f=json' |
jq '.parameterGroups[] | select(.name == "Lake/Reservoir Storage") | {name, members}'Live response summary on June 4, 2026:
Station-style collections advertise locations. Use a
bbox first.
GET /collections/snotel-edr/locations?f=json&bbox=-107,37,-105,40 HTTP/1.1
Host: api.wwdh.internetofwater.app
Accept: application/geo+json, application/json
Full URL:
https://api.wwdh.internetofwater.app/collections/snotel-edr/locations?f=json&bbox=-107,37,-105,40
Equivalent curl:
curl -sS \
'https://api.wwdh.internetofwater.app/collections/snotel-edr/locations?f=json&bbox=-107,37,-105,40'Expected response shape:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "303",
"geometry": {
"type": "Point",
"coordinates": [-105.0, 37.3]
},
"properties": {
"stationTriplet": "303:CO:SNTL",
"name": "Apishapa",
"stateCode": "CO",
"networkCode": "SNTL"
}
}
]
}The exact fields vary by provider. For RISE reservoirs you may see
properties such as _id, locationName,
timezone, elevation, and
projectNames. For SNOTEL you may see
stationTriplet, stationId,
stateCode, networkCode, name, and
huc.
/locations/{locId}When you already know a provider’s location id, request data through the location endpoint. This example asks RISE for Lake Mead daily storage for January 2023.
GET /collections/rise-edr/locations/3514?f=json&datetime=2023-01-01/2023-01-31¶meter-name=3 HTTP/1.1
Host: api.wwdh.internetofwater.app
Accept: application/prs.coverage+json, application/json
Full URL:
https://api.wwdh.internetofwater.app/collections/rise-edr/locations/3514?f=json&datetime=2023-01-01/2023-01-31¶meter-name=3
Equivalent curl:
curl -sS \
'https://api.wwdh.internetofwater.app/collections/rise-edr/locations/3514?f=json&datetime=2023-01-01/2023-01-31¶meter-name=3'Expected response shape:
{
"type": "Coverage",
"domain": {
"domainType": "PointSeries",
"axes": {
"x": {"values": [-114.7]},
"y": {"values": [36.0]},
"t": {"values": ["2023-01-01T07:00:00Z"]}
}
},
"ranges": {
"3": {
"type": "NdArray",
"axisNames": ["t"],
"values": [25000000]
}
}
}CoverageJSON is compact and array-oriented. To make a table, pair the
axis values in domain.axes with the values in each
ranges parameter.
/cubeUse cube for bbox-based bulk retrieval when the
collection advertises it. This is usually the best route for multiple
stations or a small grid.
SNOTEL SWE example:
GET /collections/snotel-edr/cube?f=json&bbox=-106.2,39.6,-105.8,40.0&datetime=2024-02-01/2024-04-30¶meter-name=WTEQ HTTP/1.1
Host: api.wwdh.internetofwater.app
Accept: application/prs.coverage+json, application/json
Full URL:
https://api.wwdh.internetofwater.app/collections/snotel-edr/cube?f=json&bbox=-106.2,39.6,-105.8,40.0&datetime=2024-02-01/2024-04-30¶meter-name=WTEQ
PRISM grid example with two parameters:
curl -sS \
'https://api.wwdh.internetofwater.app/collections/usgs-prism/cube?f=json&bbox=-112.6,36.7,-112.2,37.1&datetime=2023-01-01/2023-02-28¶meter-name=ppt,tmx'Expected CoverageJSON shape:
{
"type": "CoverageCollection",
"parameters": {
"ppt": {
"type": "Parameter",
"description": {"en": "Mean monthly precipitation"}
}
},
"coverages": [
{
"type": "Coverage",
"domain": {
"domainType": "Grid",
"axes": {
"x": {"values": [-112.56, -112.52]},
"y": {"values": [37.06, 37.02]},
"t": {"values": ["2023-01-01"]}
}
},
"ranges": {
"ppt": {
"axisNames": ["t", "y", "x"],
"values": [116.0, 120.0]
}
}
}
]
}Station collections may also return CoverageCollection,
but each coverage is often a PointSeries rather than a
Grid.
/positionPRISM advertises position, which is useful when you want
the time series at one longitude/latitude rather than a full grid.
GET /collections/usgs-prism/position?f=json&coords=POINT(-112.4%2036.9)&datetime=2023-01-01/2023-02-28¶meter-name=ppt HTTP/1.1
Host: api.wwdh.internetofwater.app
Accept: application/prs.coverage+json, application/json
Full URL:
https://api.wwdh.internetofwater.app/collections/usgs-prism/position?f=json&coords=POINT(-112.4%2036.9)&datetime=2023-01-01/2023-02-28¶meter-name=ppt
Equivalent curl:
curl -sS \
'https://api.wwdh.internetofwater.app/collections/usgs-prism/position?f=json&coords=POINT(-112.4%2036.9)&datetime=2023-01-01/2023-02-28¶meter-name=ppt'Expected response:
/areaWhen a collection advertises area, send a WKT polygon in
coords. Use this for a watershed, district, or management
boundary after you have simplified it enough for a URL.
Readable polygon:
POLYGON((-106 39,-105.8 39,-105.8 39.2,-106 39.2,-106 39))
Raw request shape:
GET /collections/snotel-edr/area?f=json&coords=POLYGON((-106%2039,-105.8%2039,-105.8%2039.2,-106%2039.2,-106%2039))&datetime=2024-02-01/2024-04-30¶meter-name=WTEQ HTTP/1.1
Host: api.wwdh.internetofwater.app
Accept: application/prs.coverage+json, application/json
For complex polygons, prefer an HTTP client that builds and encodes query parameters for you. Long URLs can hit browser, proxy, or server limits.
/queryables and
/itemsCollections without EDR data_queries are often feature
layers. Use queryables to inspect attributes and
items to retrieve features. EDR collections may also expose
/items; in that case, treat /items as the
feature catalog you use to discover stations, reservoirs, dams, and
other locations before making a data query.
Queryables example:
Expected response shape:
{
"type": "object",
"properties": {
"geometry": {"$ref": "https://geojson.org/schema/Geometry.json"},
"name": {"type": "string"},
"geoconnex_url": {"type": "string"},
"id": {"type": "string"},
"basin_index": {"type": "number"}
}
}Items example:
curl -sS \
'https://api.wwdh.internetofwater.app/collections/snotel-huc06-means/items?f=json&limit=3'Expected response shape:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "090400",
"geometry": {"type": "MultiPolygon"},
"properties": {
"name": "Upper South Saskatchewan River",
"basin_index": 75.4,
"current_snow_water_equivalent_relative_to_thirty_year_avg": 75.4,
"latest_full_day_of_data": "06-03"
}
}
]
}Feature/catalog layers are not time-series sampling endpoints. Treat them as GIS feature downloads with attributes.
/items with queryablesThe property names advertised by /queryables can usually
be sent as ordinary query parameters on /items. Combine
them with bbox and limit to find a manageable
set of features.
RISE example: find lake/reservoir features in a Lower Colorado / Arizona bbox.
curl -sS \
'https://api.wwdh.internetofwater.app/collections/rise-edr/items?f=json&bbox=-115,33,-111,35&locationTypeName=Lake%2FReservoir&limit=20'Verified response summary on June 4, 2026:
{
"type": "FeatureCollection",
"count": 7,
"sample": [
{
"id": 3515,
"locationName": "Lake Havasu Parker Dam and Powerplant",
"locationTypeName": "Lake/Reservoir",
"coords": [-114.1385, 34.2964]
}
]
}USACE example: find South Pacific Division features in the same bbox.
curl -sS \
'https://api.wwdh.internetofwater.app/collections/usace-edr/items?f=json&bbox=-115,33,-111,35&provider=SPL&limit=5'Verified response summary on June 4, 2026:
{
"type": "FeatureCollection",
"count": 4,
"sample": [
{
"id": 157145,
"name": "Alamo Dam",
"code": "Alamo",
"state": "AZ",
"provider": "SPL",
"coords": [-113.6017, 34.23167]
}
]
}Provider behavior can vary. In the RISE example above,
limit=5 with the same bbox and property filter returned no
lake/reservoir features, while limit=20 returned seven.
When using a queryable property filter for discovery, start with a large
enough limit, inspect the returned features, and then
narrow the request.
This workflow shows a complete verified pattern:
The shared bbox used here is a small area containing one matching RISE feature and one matching USACE feature:
-114.3,34.1,-113.5,34.4
It contains USBR RISE lake/reservoir features such as Lake Havasu
Parker Dam and Powerplant (3515) and USACE features such as
Alamo Dam (157145).
From parameterGroups,
Lake/Reservoir Storage includes:
| Collection | Parameter to request | Notes |
|---|---|---|
rise-edr |
3 |
Daily lake/reservoir storage, acre-ft |
usace-edr |
Flood Storage |
Flood storage, acre-ft |
usace-edr |
Conservation Storage |
Conservation storage, acre-ft; not every location has data |
For the verified USACE 2024 example below, Flood Storage
has data in the selected bbox. Conservation Storage
returned HTTP 204 No Content for the tested Hoover and Alamo examples on
June 4, 2026.
Ask for all matching RISE storage series in the bbox with
cube:
curl -sS \
'https://api.wwdh.internetofwater.app/collections/rise-edr/cube?f=json&bbox=-114.3,34.1,-113.5,34.4&datetime=2024-01-01/2024-12-31¶meter-name=3'Verified response summary:
{
"type": "CoverageCollection",
"parameterKeys": ["3"],
"coverageCount": 1,
"sample": [
{
"x": -114.1385,
"y": 34.2964,
"t_count": 365,
"t_first": "2024-01-01T07:00:00+00:00",
"t_last": "2024-12-30T07:00:00+00:00",
"value_count": 365,
"first_values": [578564.0, 579452.0, 579225.0]
}
]
}This narrow bbox returned one matching RISE storage series. If a
broad bbox fails or is too slow, discover candidate locations with
/items first and use /locations/{locId} for
one location at a time.
Ask USACE for all matching Flood Storage series in the
same bbox:
curl -sS \
'https://api.wwdh.internetofwater.app/collections/usace-edr/cube?f=json&bbox=-114.3,34.1,-113.5,34.4&datetime=2024-01-01/2024-12-31¶meter-name=Flood%20Storage'Verified response summary:
{
"type": "CoverageCollection",
"parameterKeys": ["Flood Storage"],
"coverageCount": 1,
"sample": [
{
"x": -113.6017,
"y": 34.23167,
"t_count": 8300,
"t_first": "2024-01-01T00:00:00+00:00",
"t_last": "2024-12-31T00:00:00+00:00",
"value_count": 8300,
"first_values": [136286.17, 136286.17, 136286.17]
}
]
}USACE observations in this response are not daily; they are the available observations returned by Access2Water for the 2024 interval.
The practical pattern is:
/collections?f=json
-> parameterGroups for concept mapping
/collections/{id}/queryables?f=json
-> feature property names
/collections/{id}/items?f=json&bbox=...&{queryable}=...
-> candidate location ids
/collections/{id}/locations/{locId}?f=json&datetime=2024-01-01/2024-12-31¶meter-name=...
-> full 2024 series for one location
/collections/{id}/cube?f=json&bbox=...&datetime=2024-01-01/2024-12-31¶meter-name=...
-> all matching series in a bbox, when the provider can satisfy it
You will usually see one of these JSON shapes.
GeoJSON feature collection:
GeoJSON single feature:
CoverageJSON single coverage:
CoverageJSON collection:
A simple tabular interpretation of CoverageJSON is:
coverage id + parameter id + datetime + x + y + z + value
The hard part is axis ordering.
ranges.{parameter}.axisNames tells you the order of
dimensions for the flat values array.
Use this sequence in any tool:
GET /collections?f=jsoncollection.id.GET /collections/{id}?f=jsonparameter_names exists, choose exact parameter
keys.data_queries.locations exists, inspect
/collections/{id}/locations?f=json&bbox=....data_queries.cube exists, retrieve data with
/collections/{id}/cube?f=json&bbox=...&datetime=...¶meter-name=..../collections/{id}/queryables?f=json and
/collections/{id}/items?f=json&{queryable}=....| Symptom | Likely cause | Fix |
|---|---|---|
404 on /cube, /area, or
/position |
Collection does not advertise that route | Re-check /collections/{id}?f=json |
| 400 on a WKT request | coords is not URL-encoded or WKT is invalid |
Encode spaces as %20; close polygon rings |
| 204 with an empty body | The request is valid, but no data matched the location, time, or parameter | Try another parameter from parameterGroups, another
location, or a shorter date range |
| 500 on a broad request | Upstream provider or wrapper could not satisfy the query | Reduce bbox, time range, and parameter count |
Empty features |
No matching features, or filter combination is too narrow | Try HTML page, known active bbox, or fewer filters |
NoApplicableCode query error on
/items |
Provider could not apply that feature-property filter | Try a different queryable, use only bbox and
limit, or inspect the HTML view |
No parameter_names |
It may be a feature layer | Use /queryables and /items |
| Unexpected content type | Server returns GeoJSON/CoverageJSON under JSON | Inspect the body type field |
| Shell error before request is sent | URL was not quoted | Wrap URL in single quotes |
Keep requests small while exploring. A good first request has one bbox, one parameter, and a short date range. Once it succeeds, widen only one dimension at a time.