Glossary

A reference for the domain concepts and project-specific terms used throughout pegel-online-cli. The PEGELONLINE domain is German; this glossary gives the English term used in the CLI/API alongside the original German where one exists.

Quick orientation. PEGELONLINE publishes near-real-time water levels (and a few other measured quantities) for the German federal waterway network. The hierarchy is: a body of water (Gewässer) carries stations (Pegel/gauges); each station carries one or more timeseries (e.g. W water level, Q flow); each timeseries has a current measurement and a history of measurements, and may publish characteristic values (gauge marks). The CLI mirrors that hierarchy.


The PEGELONLINE service

PEGELONLINE. The open water-level web service at pegelonline.wsv.de. It serves near-real-time and historical gauge readings for German federal waterways as a public, read-only REST API. No API key or authentication is required.

WSV — Wasserstraßen- und Schifffahrtsverwaltung des Bundes. The German Federal Waterways and Shipping Administration, which operates the gauges and publishes PEGELONLINE.

REST API v2. The version of the API this client targets. The base path is /webservices/rest-api/v2, rooted at the default base URL https://www.pegelonline.wsv.de. Every endpoint returns JSON (the client requests the .json representation of each resource).


Resources and endpoints

Stations (stations). The collection of measuring stations. GET /stations.json lists/filters them; GET /stations/{station}.json fetches one. CLI: stations list, stations get. Client: client.stations.list(), client.stations.get().

Waters (waters). The list of all bodies of water (Gewässer) covered by the service. GET /waters.json. CLI: waters. Client: client.waters().

Timeseries ({timeseries}). A single measured quantity at a station. GET /stations/{station}/{timeseries}.json returns its metadata. CLI: timeseries <station> [timeseries]. Client: client.timeseries.get().

Current measurement (currentmeasurement). The most recent reading of a timeseries. GET /stations/{station}/{timeseries}/currentmeasurement.json. CLI: current <station> [timeseries]. Client: client.timeseries.currentMeasurement().

Measurements (measurements). A time window of readings of a timeseries. GET /stations/{station}/{timeseries}/measurements.json. CLI: measurements <station> [timeseries] [--start] [--end]. Client: client.timeseries.measurements().

Characteristic values (characteristicValues). The gauge marks / characteristic values published for a timeseries (see Characteristic values below). There is no separate command or method for them: they are an embed on the station request, GET /stations/{station}.json?includeTimeseries=true&includeCharacteristicValues=true, and sit in each timeseries[] entry. CLI: stations get <station> --include-characteristic (implies --include-timeseries). Client: client.stations.get(station, { includeCharacteristicValues: true }).


Stations and waters

Pegel (station / gauge). A measuring station on a waterway. Modelled by the Station type. Key fields:

Station selector (<station>). Anywhere a station is addressed, the value may be a uuid, number, shortname or longname. The API resolves any of these forms. Names are not unique: NEUSTADT is the shortname of a gauge on the LEINE (number 48800200) and one on the OSTSEE (9610080), and the API answers a lookup by the name with one of them. So stations get, timeseries, current and measurements look a name up first (one extra request, none for a number or uuid; library: stations.assertUnique) and refuse one that names several stations — exit 2, PegelAmbiguousStationError, listing each with its number and uuid. stations list --ids NEUSTADT or --fuzzy-id lists both and prints a note; use the number or uuid of the one you mean. The CLI rejects an empty selector and the path segments . / .. for both <station> and [timeseries] before building the request URL (URL parsing would otherwise resolve them and query a different resource); the client library refuses them too, with a PegelValidationError before any request. Station and timeseries names, --ids, --waters and --fuzzy-id are sent trimmed and in composed Unicode form (NFC), so a pasted trailing space or a decomposed umlaut (KÖLN typed as KO + U+0308 + LN, common in text pasted from macOS file names or PDFs) finds the same station.

Gewässer (water / body of water). A waterway in the network, modelled by the Water type with a shortname (e.g. RHEIN) and a longname. The waters filter on stations list matches a water’s shortname.


Timeseries, measurements and units

Timeseries (TimeseriesInfo). Metadata describing one measured quantity at a station: its shortname, longname, unit, optional equidistance, an optional embedded currentMeasurement, and optional characteristicValues.

Timeseries shortname. A short code identifying the quantity. The CLI default is W (water level / Wasserstand). Other codes a station may expose include Q (flow / discharge, Durchfluss), WT (water temperature), and LT (air temperature) — availability varies per station — and the forecast series WV (see Forecast series below). The code is passed as the optional [timeseries] positional and defaults to W when omitted; a blank value is rejected as a usage error.

Unit (unit). The physical unit of a timeseries’ values, as published by the API — e.g. cm for water level, m³/s for flow, °C for temperatures. The client surfaces the API’s string verbatim. The unit belongs to the series, not to the code: not every W is in cm. On 5 October 2026, 668 of 737 W series were in cm, but 67 canal gauges (Mittellandkanal, Wesel-Datteln-, Rhein-Herne-, Elbe-Seiten-, Dortmund-Ems-, Datteln-Hamm-Kanal, Ruhr) published m+NN — metres above sea level, so MÜNSTER OW reads 56.54 — and two reservoirs (EDERTALSPERRE, DIEMELTALSPERRE) m+PNP, metres above the gauge zero. A current measurement carries no unit; read it from the series (pegel timeseries <station> <series>, or .timeseries[].unit with --include-timeseries) every time.

Equidistance (equidistance). The nominal spacing between consecutive measurements of a timeseries, in minutes (e.g. 15 for a reading every quarter hour).

Measurement (Measurement). One point of a measurements series: a timestamp (ISO-8601) and a numeric value in the timeseries’ unit — or null for a point without a reading (see No value below). Points of a forecast series also carry initialized and type.

Forecast series (WV, Wasserstandsvorhersage). A water-level forecast, not a measurement: the Bundesanstalt für Gewässerkunde (BfG) predicts the level of some gauges — on 6 October 2026 seven on the Rhine (OESTRICH, KAUB, KOBLENZ, KÖLN, DÜSSELDORF, DUISBURG-RUHRORT, EMMERICH) — about four days ahead in two-hour steps (equidistance 120). Values are in the series’ unit (cm there, like the gauge’s W). The API lists WV in a station’s timeseries only with includeForecastTimeseries (--include-forecast, which implies --include-timeseries); the series’ start/end give the forecast window and its comment the issuing run and source (“Vorhersagen und Abschätzungen vom: 06.10.2026 um 07:00 Uhr, Quelle: Bundesanstalt für Gewässerkunde”). The values come from pegel measurements <station> WV; each point carries initialized (when the run was issued) and type: forecast (Vorhersage) for the nearer points, then estimate (Abschätzung, a rougher outlook) for the later ones. `pegel current

WV` is a 404 — a forecast has no current measurement. **No value (`99999` → `null`).** Some gauges report the placeholder `99999` instead of a reading — the Rhine gauge PANNERDENSE KOP (Rijkswaterstaat) did, interleaved with real readings of 576–597 cm, on 5 October 2026. Read as a number it would be a water level of 1 km. The client and the CLI turn it into `null` wherever a reading appears (`current`, `measurements`, and the current measurement embedded by `--include-current`; constant `NO_VALUE_SENTINEL`), so a `null` `value` means "no reading at this time". Skip such points before computing a minimum, maximum or trend. **Current measurement (`CurrentMeasurement`).** The latest reading of a timeseries: a `timestamp`, a `value`, and up to two state classifications (`stateMnwMhw`, `stateNswHsw`; see below). "Latest" is not always recent: a gauge that stops reporting keeps its last reading and state, sometimes for hours, so check the `timestamp`. --- ## State classifications These string fields on a current measurement classify the reading against standard reference marks. They appear on water-level series only (not on `Q` or temperatures). The client surfaces the API's value verbatim; the API documents six: | Value | Meaning | | --- | --- | | `low` | at or below MNW (`stateMnwMhw` only) | | `normal` | between MNW and MHW (or between 0 and HSW) | | `high` | at or above MHW (or HSW) | | `unknown` | the series publishes no MNW/MHW (or HSW) mark to compare with | | `commented` | **gauge malfunction or disruption** — the value may be wrong; the reason is in the series' `comment` | | `out-dated` | the reading is older than 25 hours | A `commented` reading is not a level to judge: on 5 October 2026 RINTELN showed 92 cm, below its MNW, with the comment "Funktionsstörung, fehlerhafte Messwerte" (malfunction, faulty readings). **Comment (`comment`).** The operator's note on a disturbed timeseries — `{ shortDescription, longDescription }`, e.g. "Techn. Störung" or "Behelfspegel - Messwerte können Fehler aufweisen" (temporary gauge, values may be wrong). It is part of the timeseries (`pegel timeseries `, or `--include-timeseries` on `stations get` / `stations list`), not of the measurement; `current` alone doesn't show it. **`stateMnwMhw`.** Classification of the current value relative to the **mean low water (MNW, *Mittlerer Niedrigwasserstand*)** and **mean high water (MHW, *Mittlerer Hochwasserstand*)** marks. **`stateNswHsw`.** Classification of the current value relative to the **lowest navigable water (NSW, *Niedrigster Schifffahrtswasserstand*)** and **highest navigable water (HSW, *Höchster Schifffahrtswasserstand*)** marks — the bounds within which shipping is permitted. **Characteristic values (gauge marks).** The set of reference marks published for a timeseries (e.g. the MNW/MHW/NSW/HSW levels above). Embedded in each timeseries via `--include-characteristic` (client: `includeCharacteristicValues`) on `stations get` / `stations list`; there is no separate command. The exact shape is standard-specific, so the client returns it as a faithful raw JSON object (`JsonObject`) rather than a guessed type. --- ## Filtering, includes and the time window **`ids`.** A list of station identifiers (uuid/number/shortname/longname) to restrict a listing to. Sent to the API comma-separated. CLI: repeatable `--ids `. **`ids` / `waters` / `fuzzyId` filters.** Narrow a `stations list` by station id (`--ids`, repeatable), by water shortname (`--waters`) or by a fuzzy id match (`--fuzzy-id`). The CLI has no filter by agency or by area; filter the JSON output instead (e.g. with `jq` on `agency`, `latitude`, `longitude`). The API reads an empty parameter as no filter, so a blank value (and, in the client, an empty `ids` list) is rejected before any request: a usage error in the CLI, `PegelValidationError` in the client. **Include flags.** Optional expansions that embed extra data in a station / timeseries response, off by default: - **`includeTimeseries`** (`--include-timeseries`) — embed each station's timeseries list. - **`includeCurrentMeasurement`** (`--include-current`) — embed the current measurement inside each timeseries. - **`includeCharacteristicValues`** (`--include-characteristic`) — embed the characteristic (gauge-mark) values inside each timeseries. - **`includeForecastTimeseries`** (`--include-forecast`, station requests only) — also list the forecast series (`WV`) in the timeseries list. The API nests all three inside the timeseries list and silently drops them without `includeTimeseries`, so on a station request each one implies `includeTimeseries` (CLI and client) unless that is set explicitly. **Time window (`start` / `end`).** The bounds of a `measurements` request, as ISO-8601 instants. `start` may instead be an **ISO-8601 period/duration** such as `P7D` ("the last 7 days") or `P3D`. CLI: `--start`, `--end`. A blank value is rejected (a usage error in the CLI, `PegelValidationError` in the client) rather than silently falling back to the default window. --- ## Reliability and limits **Retry / backoff.** Transient **`429`** (Too Many Requests) and **`503`** (Service Unavailable) responses, and connections the server reset, are retried automatically, up to `maxRetries` times (default `2`; the CLI's `--max-retries` takes `0`–`10`); a timeout is not. Each retry waits 200 ms × attempt, or longer when the response's `Retry-After` (seconds or an HTTP date) asks — never shorter, so `Retry-After: 0` doesn't make a burst. A `Retry-After` above 30 s (`MAX_RETRY_AFTER_MS`) is not retried: the error surfaces at once and names the wait the server asked for. `PegelApiError` exposes `isRetryable` for these statuses. **Redirects.** The engine follows up to `maxRedirects` (default `5`) HTTP redirects (301/302/303/307/308), resolving `Location` relative to the current URL. Credentials — the userinfo of the base URL and any credential-bearing header — stay on the same origin and are dropped when a redirect crosses to another one (a 401/403 after such a hop says so). Any other 3xx (300, 304, 305), a missing or malformed `Location`, and a hop past the limit are an error (exit 1) that names the target: `redirect to not followed` (with `(stopped after 5 redirects)` at the limit) or `redirect not followed (no Location header)`. **Unencrypted base URL (`cleartextProblem`).** A base URL on plain `http:` sends every request — and the base URL's userinfo, if any — unencrypted. The engine accepts it (a local mirror may need it), but the CLI warns once per run on stderr, with a `WARN` record of `pegel.http`: `WARN [pegel.http] requests to are sent unencrypted (http:, not https:)`, or `… the base URL's credentials are sent unencrypted to …` with userinfo (never the password itself). Loopback hosts (`localhost`, `127.x.x.x`, `::1`) are exempt; stdout and the exit code are unchanged. **Timeout (`timeoutMs`).** Time limit per request in milliseconds, covering the whole response body, not only idle gaps (default `30000`; `0` disables). CLI: `--timeout`. The client enforces it for every transport, a custom one included. **Response size cap (`maxResponseBytes`).** A hard cap on response body size to defend against memory exhaustion (default 100 MiB; `0` = unlimited). CLI: `--max-response-bytes`; the error names that flag. **User-Agent (`userAgent`).** The `User-Agent` header value (default `pegel-online-cli`, used only when the option is omitted). A blank value, control characters (tab excepted) and characters above U+00FF, which an HTTP header cannot carry, are rejected up front, which also closes header injection: the client throws `PegelValidationError`, and the CLI's `--user-agent` reports a usage error (exit 2). --- ## Output and error handling **JSON output.** Every command prints JSON to stdout — pretty-printed by default, or on a single line with `--compact`. **Exit codes.** `0` success; `2` for usage/parse errors (unknown command/option, missing argument, invalid flag value, a single-value option given twice, a station name that names several stations); `4` on a `404` from the API; `1` for any other (runtime/network) error, an answer without the documented shape included. A failed run keeps its code even when nothing reads stderr. **Log record.** Every diagnostic line the CLI writes to stderr: a timestamp, a level (`ERROR`, `WARN`, `INFO`) and a topic `pegel.`, as text (log4j style) or with `--log-format jsonl` as one JSON object per line. The areas: `cli` (usage errors, commander's messages, an ambiguous station name, unexpected errors), `api` (the API's answers: an error status, a malformed answer — bad JSON, the wrong shape, an unknown charset —, and the notes below), `http` (the connection, the cleartext warning, and one WARN per retry before it waits) and `output` (a failed write to stdout). A record is always one line; control characters in it are escaped. **Notes.** `stations list` logs `INFO` records of `pegel.api` on stderr — still with exit `0` — when an `--ids` entry, `--waters` or `--fuzzy-id` matched no station (`INFO [pegel.api] --ids "KOELN" matched no station; …`), and when two listed stations share a name (`INFO [pegel.api] "NEUSTADT" names 2 stations: …`). --- > **Library & internals.** Terms for the TypeScript client and its internals — > `PegelOnlineClient`, the request engine, transport, retry/backoff, error > types, query builder — live in **[DEVELOPING.md](https://github.com/maschinenlesbar-org/pegel-online-cli/blob/v0.6.1/DEVELOPING.md)**.

View the source on GitHub →