Western Water Datahub EDR with raw HTTP

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.

1. Base URL and response formats

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:

curl -sS 'https://api.wwdh.internetofwater.app/collections?f=json'

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").

2. Discovery first

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:

curl -sS 'https://api.wwdh.internetofwater.app/collections?f=json'

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:

curl -sS 'https://api.wwdh.internetofwater.app/collections/snotel-edr?f=json'

Look for:

3. Endpoint map

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.

4. Common query parameters

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/..

5. URL encoding rules

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.

6. Finding parameters

Collection metadata is where EDR data variables live.

curl -sS 'https://api.wwdh.internetofwater.app/collections/usgs-prism?f=json'

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:

{
  "name": "Lake/Reservoir Storage",
  "members": {
    "rise-edr": ["1470", "56", "51", "47", "3"],
    "usace-edr": [
      "Flood Storage",
      "Conservation Storage",
      "Percent Flood Pool",
      "Percent Conservation Pool"
    ],
    "resviz-edr": ["avg", "p10", "p90", "raw"],
    "teacup-edr": ["avg", "p10", "p90", "raw"]
  }
}

7. Finding stations or locations

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.

8. One known station: /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&parameter-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&parameter-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&parameter-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.

9. Many stations or grid cells: /cube

Use 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&parameter-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&parameter-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&parameter-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.

10. One point from a grid: /position

PRISM 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&parameter-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&parameter-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&parameter-name=ppt'

Expected response:

{
  "type": "Coverage",
  "domain": {
    "domainType": "PointSeries",
    "axes": {
      "x": {"values": [-112.4]},
      "y": {"values": [36.9]},
      "t": {"values": ["2023-01-01", "2023-02-01"]}
    }
  },
  "ranges": {
    "ppt": {
      "axisNames": ["t"],
      "values": [116.0, 42.0]
    }
  }
}

11. Polygon queries: /area

When 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&parameter-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.

12. Feature layers: /queryables and /items

Collections 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:

curl -sS \
  'https://api.wwdh.internetofwater.app/collections/snotel-huc06-means/queryables?f=json'

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.

Filtering /items with queryables

The 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.

13. Cross-provider storage workflow

This workflow shows a complete verified pattern:

  1. Find equivalent storage parameters.
  2. Find a bbox that has both USBR RISE and USACE features.
  3. Ask each provider for all available 2024 storage observations.

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).

Step 1: choose equivalent parameters

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.

Step 2: request USBR RISE storage in the bbox for 2024

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&parameter-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.

Step 3: request USACE storage in the bbox for 2024

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&parameter-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&parameter-name=...
  -> full 2024 series for one location
/collections/{id}/cube?f=json&bbox=...&datetime=2024-01-01/2024-12-31&parameter-name=...
  -> all matching series in a bbox, when the provider can satisfy it

14. Response types in practice

You will usually see one of these JSON shapes.

GeoJSON feature collection:

{
  "type": "FeatureCollection",
  "features": []
}

GeoJSON single feature:

{
  "type": "Feature",
  "geometry": {},
  "properties": {}
}

CoverageJSON single coverage:

{
  "type": "Coverage",
  "domain": {
    "domainType": "PointSeries",
    "axes": {}
  },
  "ranges": {}
}

CoverageJSON collection:

{
  "type": "CoverageCollection",
  "parameters": {},
  "coverages": []
}

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.

15. A language-agnostic workflow

Use this sequence in any tool:

  1. GET /collections?f=json
  2. Pick collection.id.
  3. GET /collections/{id}?f=json
  4. If parameter_names exists, choose exact parameter keys.
  5. If data_queries.locations exists, inspect /collections/{id}/locations?f=json&bbox=....
  6. If data_queries.cube exists, retrieve data with /collections/{id}/cube?f=json&bbox=...&datetime=...&parameter-name=....
  7. If you need to discover feature ids or filter a feature catalog, use /collections/{id}/queryables?f=json and /collections/{id}/items?f=json&{queryable}=....
  8. Parse GeoJSON as features or CoverageJSON as axis/range arrays.
  9. Expand the spatial or temporal scope only after the small request works.

16. Troubleshooting raw requests

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.