This guide shows the same Western Water Datahub workflow in four toggleable forms:
- HTTP: the URL or request shape you can paste into a browser, curl, Postman, or another HTTP client.
- Python: raw
requestsexamples. - R (raw): raw
httr2andjsonliteexamples. - edr4r: higher-level helpers that wrap the same routes.
The examples use https://api.wwdh.internetofwater.app as the base URL.
Re-run discovery before important work because the Datahub is
provider-backed and collection behavior can change.
1. Set the base URL and response format
Most API calls use f=json. Collection metadata, GeoJSON, and
CoverageJSON are all JSON-family responses.
GET /collections?f=json HTTP/1.1
Host: api.wwdh.internetofwater.app
Accept: application/json
Full URL:
https://api.wwdh.internetofwater.app/collections?f=json
import requests
BASE = "https://api.wwdh.internetofwater.app"
response = requests.get(
f"{BASE}/collections",
params={"f": "json"},
timeout=30,
)
response.raise_for_status()
payload = response.json()
library(httr2)
library(jsonlite)
base <- "https://api.wwdh.internetofwater.app"
response <- request(base) |>
req_url_path_append("collections") |>
req_url_query(f = "json") |>
req_perform()
payload <- resp_body_json(response, simplifyVector = FALSE)
library(edr4r)
wwdh <- edr_client("https://api.wwdh.internetofwater.app")
collections <- edr_collections(wwdh)
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 or location endpoints | CSV table |
jsonld |
Linked-data representation when advertised | JSON-LD |
Expected output: A successful request returns a JSON document, not a
rendered table. For /collections, the top-level keys include links,
collections, and sometimes parameterGroups.
Example JSON shape:
{
"links": [
{
"rel": "self",
"type": "application/json",
"href": "https://api.wwdh.internetofwater.app/collections?f=json"
}
],
"collections": [
{
"id": "rise-edr",
"title": "USBR Reclamation Information Sharing Environment (RISE)"
}
]
}
How to read it: Confirm the response is HTTP 200 and the body is
JSON. If a browser shows a formatted HTML page instead, check that the
URL includes f=json.
Next decision: Move from the service-level response to the collection list and choose the dataset family you want to inspect.
2. List collections and identify dataset families
/collections is the catalog. Each collection has an id, title,
extent, links, and sometimes data_queries. If data_queries
contains locations, cube, or area, the collection supports EDR
sampling routes. If it does not, it is often a feature layer accessed
through /items.
curl -sS 'https://api.wwdh.internetofwater.app/collections?f=json'
Look for collections[].id, collections[].title, and
collections[].data_queries.
collections = payload["collections"]
for collection in collections:
data_queries = collection.get("data_queries") or {}
print(
collection["id"],
collection["title"],
list(data_queries.keys()),
sep=" | ",
)
collections <- payload$collections
`%||%` <- function(x, y) if (is.null(x)) y else x
data.frame(
id = vapply(collections, `[[`, character(1), "id"),
title = vapply(collections, `[[`, character(1), "title"),
data_queries = vapply(
collections,
function(x) paste(names(x$data_queries %||% list()), collapse = ", "),
character(1)
)
)
collections <- edr_collections(wwdh)
collections[, c("id", "title", "data_queries")]
Examples of current collection families include reservoir operations
(rise-edr, usace-edr, resviz-edr, teacup-edr, resops-edr),
snow station data (snotel-edr, awdb-forecasts-edr), basin snow
summaries (snotel-huc06-means), NOAA forecast layers, drought monitor
layers, and DOI/USBR reference boundaries.
Expected output: A collection listing, with one record per dataset.
Each useful row has at least an id, a title, links, an extent, and
possibly a data_queries object.
Example JSON shape:
{
"collections": [
{
"id": "snotel-edr",
"title": "USDA Snowpack Telemetry Network (SNOTEL)",
"data_queries": {
"locations": {},
"cube": {},
"area": {},
"items": {}
}
},
{
"id": "snotel-huc06-means",
"title": "USDA Snotel Snow Water Equivalent Aggregated by HUC6"
}
]
}
How to read it: Treat id as the value you put in URLs. A non-empty
data_queries object means the collection advertises EDR sampling
routes such as locations, cube, or area. A missing or empty
data_queries object usually means the collection is a feature layer
best explored through /items.
Next decision: Pick one collection id, then inspect that collection before requesting data values.
3. Inspect one collection
Before retrieving values, inspect the collection metadata. This tells you which parameters and query routes are valid.
curl -sS 'https://api.wwdh.internetofwater.app/collections/snotel-edr?f=json'
Important fields:
parameter_names: variables to request withparameter-name.data_queries: supported EDR routes.links: HTML, JSON,/items,/queryables, and documentation links.extent: rough spatial and temporal coverage.
collection_id = "snotel-edr"
meta = requests.get(
f"{BASE}/collections/{collection_id}",
params={"f": "json"},
timeout=30,
)
meta.raise_for_status()
collection = meta.json()
print(collection["title"])
print("queries:", list((collection.get("data_queries") or {}).keys()))
print("parameters:", list((collection.get("parameter_names") or {}).keys())[:20])
collection_id <- "snotel-edr"
response <- request(base) |>
req_url_path_append("collections", collection_id) |>
req_url_query(f = "json") |>
req_perform()
collection <- resp_body_json(response, simplifyVector = FALSE)
collection$title
names(collection$data_queries)
names(collection$parameter_names)[1:20]
collection <- edr_collection(wwdh, "snotel-edr")
names(collection$data_queries)
params <- edr_parameters(wwdh, "snotel-edr")
params[, c("id", "name", "unit_symbol")]
Use exact parameter ids in requests. For example, SNOTEL snow water
equivalent is WTEQ; USACE flood storage is Flood Storage; RISE
parameter ids are numeric strings such as 3 or 1830.
Expected output: One collection metadata document. For an EDR
collection, expect fields such as title, extent, links,
data_queries, and parameter_names.
Example JSON shape:
{
"id": "snotel-edr",
"title": "USDA Snowpack Telemetry Network (SNOTEL)",
"parameter_names": {
"WTEQ": {
"type": "Parameter",
"description": {"en": "Snow Water Equivalent"}
},
"SNWD": {
"type": "Parameter",
"description": {"en": "Snow Depth"}
}
},
"data_queries": {
"locations": {},
"cube": {},
"area": {},
"items": {}
}
}
How to read it: Use parameter_names keys exactly as
parameter-name values. Use data_queries to decide which routes are
valid. Use links to find related HTML pages, /items, /queryables,
schemas, and source documentation.
Next decision: If you need to find places, continue to /locations
or /items. If you already know a location id, skip ahead to a value
request.
4. Find locations or features
Use /locations for station-style EDR collections. Use /items for
feature layers, catalogs, boundaries, forecast layers, and collections
without advertised EDR data queries.
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
locations = requests.get(
f"{BASE}/collections/snotel-edr/locations",
params={"f": "json", "bbox": "-107,37,-105,40"},
timeout=30,
)
locations.raise_for_status()
geojson = locations.json()
for feature in geojson["features"][:5]:
print(feature["id"], feature["properties"].get("name"))
response <- request(base) |>
req_url_path_append("collections", "snotel-edr", "locations") |>
req_url_query(f = "json", bbox = "-107,37,-105,40") |>
req_perform()
geojson <- resp_body_json(response, simplifyVector = FALSE)
vapply(
geojson$features[1:5],
function(feature) paste(feature$id, feature$properties$name),
character(1)
)
sites <- edr_locations(
wwdh,
"snotel-edr",
bbox = c(-107, 37, -105, 40)
)
sites[, c("stationTriplet", "name", "stateCode", "networkCode")]
GeoJSON features describe places. For station collections, the feature
id is usually the value to use in /locations/{locId}. Properties
such as station name, network code, elevation, or project name vary by
provider.
Expected output: A GeoJSON FeatureCollection. Each feature has an
id, a geometry, and a properties object with provider-specific
metadata.
Example JSON shape:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": 303,
"geometry": {
"type": "Point",
"coordinates": [-105.06766, 37.33067]
},
"properties": {
"stationTriplet": "303:CO:SNTL",
"name": "Apishapa",
"stateCode": "CO",
"networkCode": "SNTL"
}
}
]
}
How to read it: Use geometry to locate the site or feature. Use
the feature id as the stable API identifier unless the provider
documents another id field. Use properties for labels, station codes,
network names, elevations, project names, and other context. If
features is empty, the bbox or filters did not match anything, or the
provider did not honor that filter combination.
Next decision: Choose a feature or location id for a single-site
query, or keep the bbox and use /cube for a bulk request.
5. Request one known location
Once you know a location id, request a traditional single-site time
series through /locations/{locId}. This example uses SNOTEL location
303 and snow water equivalent (WTEQ).
GET /collections/snotel-edr/locations/303?f=json&datetime=2024-02-01/2024-02-07¶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/locations/303?f=json&datetime=2024-02-01/2024-02-07¶meter-name=WTEQ
series = requests.get(
f"{BASE}/collections/snotel-edr/locations/303",
params={
"f": "json",
"datetime": "2024-02-01/2024-02-07",
"parameter-name": "WTEQ",
},
timeout=30,
)
series.raise_for_status()
coverage = series.json()
response <- request(base) |>
req_url_path_append("collections", "snotel-edr", "locations", "303") |>
req_url_query(
f = "json",
datetime = "2024-02-01/2024-02-07",
`parameter-name` = "WTEQ"
) |>
req_perform()
coverage <- resp_body_json(response, simplifyVector = FALSE)
swe <- edr_location(
wwdh,
"snotel-edr",
loc_id = "303",
datetime = "2024-02-01/2024-02-07",
parameter_name = "WTEQ"
)
swe_tbl <- covjson_to_tibble(swe)
The response is CoverageJSON. For one site and one parameter, it is
usually a PointSeries: one x, one y, many t values, and one
range array.
Expected output: A CoverageJSON Coverage or CoverageCollection.
For this example, expect a PointSeries with domain.axes.x,
domain.axes.y, domain.axes.t, and a ranges.WTEQ.values array.
Example JSON shape:
{
"type": "Coverage",
"domain": {
"domainType": "PointSeries",
"axes": {
"x": {"values": [-105.06766]},
"y": {"values": [37.33067]},
"t": {
"values": [
"2024-02-01T00:00:00Z",
"2024-02-02T00:00:00Z"
]
}
}
},
"ranges": {
"WTEQ": {
"type": "NdArray",
"axisNames": ["t"],
"values": [11.6, 11.7]
}
}
}
How to read it: x and y identify the site location. t lists
the returned timestamps. ranges.WTEQ.values contains the SWE values,
aligned to the axis order in ranges.WTEQ.axisNames. In edr4r, the
tidy table should have one row per timestamp with datetime and
value columns.
Next decision: Flatten the coverage into rows for analysis, or move
from one location to a bbox-based /cube request.
6. Request many sites in a bounding box
Use /cube when a collection advertises it and you want values for
many stations, reservoirs, or spatial cells in one request. Keep the
bbox and time window small while exploring.
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
cube = requests.get(
f"{BASE}/collections/snotel-edr/cube",
params={
"f": "json",
"bbox": "-106.2,39.6,-105.8,40.0",
"datetime": "2024-02-01/2024-04-30",
"parameter-name": "WTEQ",
},
timeout=60,
)
cube.raise_for_status()
coverage_collection = cube.json()
response <- request(base) |>
req_url_path_append("collections", "snotel-edr", "cube") |>
req_url_query(
f = "json",
bbox = "-106.2,39.6,-105.8,40.0",
datetime = "2024-02-01/2024-04-30",
`parameter-name` = "WTEQ"
) |>
req_timeout(60) |>
req_perform()
coverage_collection <- resp_body_json(response, simplifyVector = FALSE)
swe <- edr_cube(
wwdh,
"snotel-edr",
bbox = c(-106.2, 39.6, -105.8, 40.0),
datetime = "2024-02-01/2024-04-30",
parameter_name = "WTEQ"
)
swe_tbl <- covjson_to_tibble(swe)
For reservoir collections, the same pattern works with reservoir
parameters. For example, RISE reservoir storage often uses parameter id
3, and USACE outflow uses Outflow.
Expected output: A CoverageJSON CoverageCollection or Coverage
containing the values found inside the bbox and time window. Station
collections often return one coverage per matched site; grid-like
collections may return one coverage with multi-axis arrays.
Example JSON shape:
{
"type": "CoverageCollection",
"coverages": [
{
"type": "Coverage",
"domain": {
"domainType": "PointSeries",
"axes": {
"x": {"values": [-105.06766]},
"y": {"values": [37.33067]},
"t": {"values": ["2024-02-01T00:00:00Z"]}
}
},
"ranges": {
"WTEQ": {
"axisNames": ["t"],
"values": [11.6]
}
}
}
]
}
How to read it: Inspect each coverage’s domain to see which place,
cell, or time axis it represents. Inspect ranges for the requested
parameter values. If the response is much smaller than expected, the
bbox, datetime interval, or parameter may not overlap available data.
Next decision: Flatten the coverage into a table, join it to site metadata, or broaden only one dimension at a time.
7. Query feature layers with /queryables and /items
Feature layers expose geometries and attributes. They may be basin
summaries, forecast layers, drought polygons, boundaries, or catalog
records. Start with /queryables, then request /items.
curl -sS 'https://api.wwdh.internetofwater.app/collections/snotel-huc06-means/queryables?f=json'
curl -sS 'https://api.wwdh.internetofwater.app/collections/snotel-huc06-means/items?f=json&limit=3'
queryables = requests.get(
f"{BASE}/collections/snotel-huc06-means/queryables",
params={"f": "json"},
timeout=30,
)
queryables.raise_for_status()
print(queryables.json()["properties"].keys())
items = requests.get(
f"{BASE}/collections/snotel-huc06-means/items",
params={"f": "json", "limit": 3},
timeout=30,
)
items.raise_for_status()
features = items.json()["features"]
queryables <- request(base) |>
req_url_path_append("collections", "snotel-huc06-means", "queryables") |>
req_url_query(f = "json") |>
req_perform() |>
resp_body_json(simplifyVector = FALSE)
names(queryables$properties)
items <- request(base) |>
req_url_path_append("collections", "snotel-huc06-means", "items") |>
req_url_query(f = "json", limit = 3) |>
req_perform() |>
resp_body_json(simplifyVector = FALSE)
queryables <- edr_queryables(wwdh, "snotel-huc06-means")
names(queryables$properties)
huc_swe <- edr_items(
wwdh,
"snotel-huc06-means",
limit = 3
)
Feature responses are GeoJSON FeatureCollections. They describe features and attributes, not CoverageJSON time-series arrays.
Expected output: /queryables returns a JSON Schema-like document
whose properties are filterable fields. /items returns a GeoJSON
FeatureCollection.
Example /queryables JSON shape:
{
"type": "object",
"properties": {
"geometry": {"$ref": "https://geojson.org/schema/Geometry.json"},
"name": {"type": "string"},
"basin_index": {"type": "number"}
}
}
Example /items JSON shape:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "090400",
"geometry": {"type": "MultiPolygon"},
"properties": {
"name": "Upper South Saskatchewan River",
"basin_index": 72.9,
"latest_full_day_of_data": "06-17"
}
}
]
}
How to read it: Use queryable property names as candidate filters on
/items. Read item geometry as the feature shape and item
properties as attributes such as basin names, drought categories,
forecast values, region ids, or station lists. Do not look for
CoverageJSON domain or ranges in feature responses.
Next decision: Add filters, lower limit, or add bbox until the
feature set is manageable. If the feature belongs to an EDR collection,
you may use its identifiers in a later sampling query.
8. Query an area
When a collection advertises area, send a WKT polygon in coords.
This is useful for watershed, district, or management-area queries.
Encode spaces in WKT as %20 if you write the URL by hand.
Readable WKT:
POLYGON((-106 39,-105.8 39,-105.8 39.2,-106 39.2,-106 39))
Encoded request:
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
polygon = "POLYGON((-106 39,-105.8 39,-105.8 39.2,-106 39.2,-106 39))"
area = requests.get(
f"{BASE}/collections/snotel-edr/area",
params={
"f": "json",
"coords": polygon,
"datetime": "2024-02-01/2024-04-30",
"parameter-name": "WTEQ",
},
timeout=60,
)
area.raise_for_status()
coverage = area.json()
polygon <- "POLYGON((-106 39,-105.8 39,-105.8 39.2,-106 39.2,-106 39))"
response <- request(base) |>
req_url_path_append("collections", "snotel-edr", "area") |>
req_url_query(
f = "json",
coords = polygon,
datetime = "2024-02-01/2024-04-30",
`parameter-name` = "WTEQ"
) |>
req_timeout(60) |>
req_perform()
coverage <- resp_body_json(response, simplifyVector = FALSE)
swe_area <- edr_area(
wwdh,
"snotel-edr",
coords = "POLYGON((-106 39,-105.8 39,-105.8 39.2,-106 39.2,-106 39))",
datetime = "2024-02-01/2024-04-30",
parameter_name = "WTEQ"
)
swe_area_tbl <- covjson_to_tibble(swe_area)
For complex polygons, use a client that URL-encodes parameters for you. Very long URLs can hit browser, proxy, or server limits.
Expected output: A CoverageJSON response for values associated with the polygon query. Depending on the provider, the response may represent matched stations inside the polygon, sampled values for the area, or an aggregate-like coverage.
Example JSON shape:
{
"type": "CoverageCollection",
"coverages": [
{
"type": "Coverage",
"domain": {
"domainType": "PointSeries",
"axes": {
"x": {"values": [-105.9]},
"y": {"values": [39.1]},
"t": {"values": ["2024-02-01T00:00:00Z"]}
}
},
"ranges": {
"WTEQ": {
"axisNames": ["t"],
"values": [12.4]
}
}
}
]
}
How to read it: Start with domain.domainType and domain.axes to
understand whether the response is point-series-like, area-like, or
multi-axis. Then read ranges for the requested parameter values. If
the request fails or returns too much data, simplify the polygon or
test the same area with a bbox first.
Next decision: Flatten the coverage into rows, or reduce the polygon/time/parameter scope and retry.
9. Flatten CoverageJSON into a table
EDR data queries usually return CoverageJSON. The data model is:
domain.axes for coordinates and ranges for parameter values.
Feature routes return GeoJSON instead.
CoverageJSON aligns axes and ranges:
{
"type": "Coverage",
"domain": {
"domainType": "PointSeries",
"axes": {
"x": {"values": [-105.0]},
"y": {"values": [37.3]},
"t": {"values": ["2024-02-01T00:00:00Z"]}
}
},
"ranges": {
"WTEQ": {
"axisNames": ["t"],
"values": [11.6]
}
}
}
The first WTEQ value belongs to the first t value at the listed
x/y coordinate.
def point_series_rows(covjson):
coverages = covjson.get("coverages") or [covjson]
rows = []
for index, coverage in enumerate(coverages, start=1):
axes = coverage["domain"]["axes"]
xs = axes.get("x", {}).get("values", [None])
ys = axes.get("y", {}).get("values", [None])
ts = axes.get("t", {}).get("values", [None])
for parameter, range_obj in coverage.get("ranges", {}).items():
values = range_obj.get("values", [])
for time_value, data_value in zip(ts, values):
rows.append({
"coverage_id": str(index),
"parameter": parameter,
"datetime": time_value,
"x": xs[0],
"y": ys[0],
"value": data_value,
})
return rows
rows = point_series_rows(coverage)
`%||%` <- function(x, y) if (is.null(x)) y else x
point_series_rows <- function(covjson) {
coverages <- covjson$coverages %||% list(covjson)
do.call(rbind, Map(function(coverage, coverage_id) {
axes <- coverage$domain$axes
x <- (axes$x$values %||% NA)[[1]]
y <- (axes$y$values %||% NA)[[1]]
times <- axes$t$values %||% list(NA)
do.call(rbind, lapply(names(coverage$ranges), function(parameter) {
values <- coverage$ranges[[parameter]]$values
data.frame(
coverage_id = coverage_id,
parameter = parameter,
datetime = unlist(times),
x = x,
y = y,
value = unlist(values)
)
}))
}, coverages, seq_along(coverages)))
}
rows <- point_series_rows(coverage)
rows <- covjson_to_tibble(swe)
rows[, c("coverage_id", "parameter", "datetime", "x", "y", "value")]
The simple raw flatteners above are for point-series coverages. Grids
and multi-axis coverages need array indexing across each axisNames
dimension. Use covjson_to_tibble() when you want that handled for
you.
Expected output: A rectangular table with columns such as
coverage_id, parameter, datetime, x, y, and value.
Example row-oriented JSON shape:
[
{
"coverage_id": "1",
"parameter": "WTEQ",
"datetime": "2024-02-01T00:00:00Z",
"x": -105.06766,
"y": 37.33067,
"value": 11.6
}
]
How to read it: Each row is the traditional observation, forecast,
or modeled-value record most analysts expect: one parameter at one
place and time. coverage_id keeps a connection back to the original
CoverageJSON object. Missing or NA values usually mean the provider
returned an explicit missing value for that coordinate/time.
Next decision: Plot, map, join, or export the table. If the coverage
is a grid or another multi-axis structure, use a parser that understands
axisNames rather than the simple point-series helper.
10. Scale up carefully
Once a small query works, enlarge one dimension at a time: wider bbox, longer time range, or more parameters. That keeps failures diagnosable and avoids stressing shared upstream services.
Small:
bbox=-106.2,39.6,-105.8,40.0
datetime=2024-02-01/2024-02-07
parameter-name=WTEQ
Then enlarge one part:
bbox=-107,37,-105,40
datetime=2024-02-01/2024-04-30
parameter-name=WTEQ,SNWD
params = {
"f": "json",
"bbox": "-106.2,39.6,-105.8,40.0",
"datetime": "2024-02-01/2024-02-07",
"parameter-name": "WTEQ",
}
# After this works, change only one key at a time.
params["datetime"] = "2024-02-01/2024-04-30"
params <- list(
f = "json",
bbox = "-106.2,39.6,-105.8,40.0",
datetime = "2024-02-01/2024-02-07",
`parameter-name` = "WTEQ"
)
# After this works, change only one value at a time.
params$datetime <- "2024-02-01/2024-04-30"
sample <- edr_cube(
wwdh,
"snotel-edr",
bbox = c(-106.2, 39.6, -105.8, 40.0),
datetime = "2024-02-01/2024-02-07",
parameter_name = "WTEQ"
)
# Then widen one dimension, for example:
larger_time_window <- edr_cube(
wwdh,
"snotel-edr",
bbox = c(-106.2, 39.6, -105.8, 40.0),
datetime = "2024-02-01/2024-04-30",
parameter_name = "WTEQ"
)
Expected output: The response shape should be the same as the small query, but with more features, coverages, timestamps, parameters, or rows after flattening.
Example JSON shape:
{
"type": "CoverageCollection",
"coverages": [
{"type": "Coverage", "ranges": {"WTEQ": {"values": [11.6]}}},
{"type": "Coverage", "ranges": {"WTEQ": {"values": [8.2]}}}
]
}
How to read it: Watch response time, payload size, row count, feature count, and missing-value patterns. If the larger request fails, the last dimension you enlarged is the most likely source of the problem.
Next decision: Cache the largest trusted result locally, then widen one more dimension only when you need it.
11. Troubleshooting raw requests
Expected output: This section does not introduce a new request. Use it when the response you got does not match the expected output from an earlier step.
Example error JSON shape:
{
"code": "InvalidParameterValue",
"description": "The requested parameter is not available for this collection."
}
How to read it: Match the observed symptom to the closest row in the table, then adjust only the likely cause before retrying. Keep the last successful small request handy so you can compare behavior.
Next decision: Return to the earlier workflow step, simplify the query, and add filters, parameters, or time range back one at a time.
| Symptom | Likely cause | What to try |
|---|---|---|
| HTTP 404 on a route | Collection does not advertise that query type | Re-check /collections/{id}?f=json |
| HTTP 400 or 500 | Query too broad, invalid parameter, or upstream provider failure | Reduce bbox, date range, and parameter count |
Empty features |
No feature matched the filters | Try /items?f=json&limit=1, then add filters back |
Empty or missing parameter_names |
Feature-style collection, not EDR sampling | Use /queryables and /items |
| URL works in Python/R but not a browser | Unencoded spaces or reserved characters | Let the client encode query params, or encode spaces as %20 |
| Values are hard to interpret | CoverageJSON is array-oriented | Pair domain.axes with ranges, or use covjson_to_tibble() |
Good habits:
- Quote shell URLs that contain
?,&, spaces, or parentheses. - Keep bbox order as
min_lon,min_lat,max_lon,max_lat. - Use exact parameter ids, not labels.
- Cache trusted results locally before doing downstream analysis.