---
title: "Western Water Datahub EDR with raw HTTP"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Western Water Datahub EDR with raw HTTP}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

This guide shows how to use the
[Western Water Datahub](https://api.wwdh.internetofwater.app) 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](https://api.wwdh.internetofwater.app/openapi)
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:

``` text
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.

``` text
GET https://api.wwdh.internetofwater.app/collections?f=json
```

You can often request a browser-readable HTML page with `f=html`:

``` text
GET https://api.wwdh.internetofwater.app/collections?f=html
```

For command-line use, quote URLs that contain `?`, `&`, parentheses, or
spaces:

``` sh
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.

``` http
GET /?f=json HTTP/1.1
Host: api.wwdh.internetofwater.app
Accept: application/json
```

Equivalent full URL:

``` text
https://api.wwdh.internetofwater.app/?f=json
```

Then list collections:

``` http
GET /collections?f=json HTTP/1.1
Host: api.wwdh.internetofwater.app
Accept: application/json
```

Equivalent `curl`:

``` sh
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:

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

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.

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

``` text
min_lon,min_lat,max_lon,max_lat
```

Multiple parameters are comma-separated:

``` text
parameter-name=ppt,tmx
```

Some parameter ids contain spaces. Encode them in a hand-built URL:

``` text
parameter-name=Flood%20Storage
```

or let `curl` encode them:

``` sh
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:

``` text
datetime=2024-01-01/2024-03-31
```

Open intervals may be supported by some providers:

``` text
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`:

``` text
coords=POINT(-112.4%2036.9)
```

A polygon must be URL-encoded if it contains spaces or parentheses.
This readable WKT:

``` text
POLYGON((-106 39,-105.8 39,-105.8 39.2,-106 39.2,-106 39))
```

becomes a query value like:

``` text
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.

``` sh
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:

``` sh
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:

``` json
{
  "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.

``` http
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:

``` text
https://api.wwdh.internetofwater.app/collections/snotel-edr/locations?f=json&bbox=-107,37,-105,40
```

Equivalent `curl`:

``` sh
curl -sS \
  'https://api.wwdh.internetofwater.app/collections/snotel-edr/locations?f=json&bbox=-107,37,-105,40'
```

Expected response shape:

``` json
{
  "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.

``` http
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:

``` text
https://api.wwdh.internetofwater.app/collections/rise-edr/locations/3514?f=json&datetime=2023-01-01/2023-01-31&parameter-name=3
```

Equivalent `curl`:

``` sh
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:

``` json
{
  "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:

``` http
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:

``` text
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:

``` sh
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:

``` json
{
  "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.

``` http
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:

``` text
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`:

``` sh
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:

``` json
{
  "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:

``` text
POLYGON((-106 39,-105.8 39,-105.8 39.2,-106 39.2,-106 39))
```

Raw request shape:

``` http
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:

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

Expected response shape:

``` json
{
  "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:

``` sh
curl -sS \
  'https://api.wwdh.internetofwater.app/collections/snotel-huc06-means/items?f=json&limit=3'
```

Expected response shape:

``` json
{
  "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.

``` sh
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:

``` json
{
  "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.

``` sh
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:

``` json
{
  "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:

``` text
-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`:

``` sh
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:

``` json
{
  "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:

``` sh
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:

``` json
{
  "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:

``` text
/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:

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

GeoJSON single feature:

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

CoverageJSON single coverage:

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

CoverageJSON collection:

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

A simple tabular interpretation of CoverageJSON is:

``` text
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.
