Usage
Real, use-case-driven examples for the pegel CLI — a command-line client for the
open PEGELONLINE REST API v2
(pegelonline.wsv.de), the live water-level service of the German federal
waterway administration (WSV). Every command prints JSON to stdout, so it pipes
cleanly into jq.
Install
npm i -g @maschinenlesbar.org/pegel-online-cli
This installs the pegel binary. Without a global install you can run the
same commands via node apps/cli/dist/src/cli/index.js … from a built checkout.
A <station> argument may be a uuid, number, shortname or
longname. Names are not unique (NEUSTADT is a LEINE and an OSTSEE gauge): given a
name, stations get, timeseries, current and measurements first look it up (one
extra request; none for a number or uuid) and refuse a name that names several stations
with exit 2 and an ERROR [pegel.cli] Invalid station "NEUSTADT": it names 2 stations, NEUSTADT
on LEINE (number 48800200, uuid …) and NEUSTADT on OSTSEE (number 9610080, uuid …); use
the number or uuid. record on stderr (it lists the first 10 such stations and counts the
rest, … (N more)). A [timeseries] argument defaults to W (water level); other
common series are Q (flow/discharge), WT (water temperature) and LT (air
temperature), depending on the station.
Use cases
1. List all bodies of water (Gewässer)
Discover the water shortnames you can later filter stations by.
pegel waters
Returns every Gewässer with its shortname and longname. Grab just the
shortnames:
pegel waters | jq -r '.[].shortname'
2. List all stations on one water
You want every gauge along the Rhine.
pegel stations list --waters RHEIN
--waters takes a water shortname (see use case 1). Reduce the output to a
name + location table:
pegel stations list --waters RHEIN | jq -r '.[] | [.shortname, .longitude, .latitude] | @tsv'
3. Current water level at a station
The single most common question: what’s the level right now at Bonn?
pegel current BONN
Defaults to the W (water level) series. For the current flow instead, name the
series explicitly:
pegel current BONN Q
Pull just the value and timestamp:
pegel current BONN | jq '{value, timestamp}'
The measurement carries no unit: it is the series’ unit — cm for most water
levels, but m+NN/m+PNP (metres) on canal and reservoir gauges. Get both at once with
pegel stations get BONN --include-current | jq '.timeseries[] | {shortname, unit,
comment, currentMeasurement}'; comment is there while the gauge is disturbed (state
commented). A value of null means the gauge sent no reading (its placeholder
99999).
4. Last N days of measurements (time window)
Plot or analyse a recent trend. --start accepts an ISO-8601 duration like
P7D (last 7 days) or an absolute instant.
pegel measurements BONN W --start P7D
Use an explicit window with both ends as ISO-8601 instants:
pegel measurements BONN W --start 2026-06-01T00:00:00Z --end 2026-06-07T00:00:00Z
Extract a CSV-ish series for a spreadsheet:
pegel measurements BONN W --start P3D | jq -r '.[] | select(.value != null) | [.timestamp, .value] | @csv'
5. Stations inside a geographic bounding box
Find all gauges in a map viewport — e.g. the Bonn/Cologne stretch of the Rhine.
Every station carries its latitude/longitude, so filter the full list to a
box with jq:
pegel stations list | jq '[.[] | select(.latitude >= 50.5 and .latitude <= 51.0 and .longitude >= 6.5 and .longitude <= 7.5)]'
The four bounds are the min/max latitude and longitude (decimal degrees, WGS84) of the box. Pull just the shortnames inside it:
pegel stations list | jq -r '.[] | select(.latitude >= 50.5 and .latitude <= 51.0 and .longitude >= 6.5 and .longitude <= 7.5) | .shortname'
6. One station with its level and timeseries embedded
Get a full snapshot of a single gauge in one call — metadata, the list of available series, and the current reading.
pegel stations get BONN --include-timeseries --include-current
The same three embed flags exist on stations list
(--include-timeseries, --include-current, --include-characteristic), so you
can fetch a water plus live levels in one request:
pegel stations list --waters RHEIN --include-current
The API nests the current measurement (and the gauge marks) inside each
station’s timeseries and drops them without the timeseries list, so
--include-current and --include-characteristic imply --include-timeseries.
The reading is at .timeseries[] | select(.shortname == "W") | .currentMeasurement.
7. Characteristic (gauge-mark) values for a station
Compare today’s level against statistical marks such as MNW/MHW (mean low/high
water) to judge flood or low-water risk. These gauge marks are exposed on the
station’s timeseries via the --include-characteristic embed:
pegel stations get KÖLN --include-timeseries --include-characteristic
Pull just the marks for the W (water level) series:
pegel stations get KÖLN --include-timeseries --include-characteristic | jq '.timeseries[] | select(.shortname == "W") | .characteristicValues'
Not every series has characteristic values; one without marks comes back with an
empty "characteristicValues": [] (at BONN, Q does; W has seven marks).
8. Timeseries metadata for a station
Discover which series a station actually offers and their units before querying measurements.
pegel timeseries BONN
List every series shortname available at a station:
pegel stations get BONN --include-timeseries | jq -r '.timeseries[].shortname'
9. Look up several specific stations at once
Fetch a fixed set of gauges by id — uuids, numbers, shortnames or longnames —
using the repeatable --ids flag.
pegel stations list --ids BONN --ids KÖLN --ids EMMERICH --include-current
--ids is repeatable; supply it once per station. For an inexact name match use
--fuzzy-id instead. The API drops an id it doesn’t know without saying so; the CLI
logs INFO [pegel.api] --ids "KOELN" matched no station; … on stderr for each such id (exit 0, the
others are listed), and the same kind of note when --waters or --fuzzy-id matched
nothing. --waters, --fuzzy-id, --start, --end and the global options take one
value: giving one twice is a usage error (exit 2).
10. Filter stations by operating agency
Narrow a list to gauges run by a particular WSV office. Each station carries its
agency, so combine a --waters filter with jq to pick one office’s gauges:
pegel stations list --waters RHEIN | jq '[.[] | select(.agency == "STANDORT KÖLN")]'
To find a station by (part of) its name instead, use the --fuzzy-id flag, which
matches the short/long name:
pegel stations list --fuzzy-id bonn
11. Water-level forecast (WV)
Some gauges carry a forecast series WV (Wasserstandsvorhersage) from the
Bundesanstalt für Gewässerkunde: predicted levels, not measurements, about four days
ahead in two-hour steps, in the series’ unit (cm on the Rhine gauges). The API lists
it only on request — --include-forecast (implies --include-timeseries) shows which
gauges of a water have one:
pegel stations list --waters RHEIN --include-forecast \
| jq -r '.[] | select(any(.timeseries[]; .shortname == "WV")) | "\(.shortname)\t\(.number)"'
The series’ start/end are the forecast window and its comment names the run and
the source. The values come from measurements; each point carries initialized (when
the forecast was issued) and type — forecast for the nearer points, then
estimate (a rougher outlook) for the later ones:
pegel measurements 2730010 WV | jq -r '.[] | [.timestamp, .value, .type] | @tsv' # KÖLN
A forecast has no current measurement: pegel current <station> WV exits 4.
Global options
These apply to every command and may be placed before or after it (before is clearer):
| Option | Description |
|---|---|
-V, --version |
Print the version and exit |
--base-url <url> |
API base URL (default https://www.pegelonline.wsv.de); http(s) only, a path prefix is fine, no query (?), fragment (#), whitespace or control characters; userinfo is sent as Basic auth but shown as *** in messages (write a literal % in it as %25). A plain http: URL to a host other than localhost, 127.x.x.x or ::1 logs one warning on stderr, WARN [pegel.http] requests to <host> are sent unencrypted (http:, not https:) (naming the base URL’s credentials when it has userinfo, never printing them); stdout and the exit code are unchanged |
--timeout <ms> |
Time limit per request in milliseconds, reading the whole response included |
--user-agent <ua> |
User-Agent header value (not blank; Latin-1, no control characters) |
--max-retries <n> |
Retries for transient 429/503 responses and reset connections, 0–10 (default 2); each waits 200 ms × attempt, or longer if the server’s Retry-After asks (up to 30 s; a longer one is not retried, and the error names the wait). A timeout is not retried. Each retry logs one WARN record of pegel.http before it waits (HTTP 503 from host: retry 1 of 3 in 2 s). |
--max-response-bytes <n> |
Cap response body size in bytes (0 = unlimited; default 100 MiB) |
--compact |
Print JSON on a single line instead of pretty-printed |
--log-format <format> |
How errors, warnings and notes are written to stderr: text (default; log4j style, 2026-10-09T14:03:12.481Z WARN [pegel.http] …) or jsonl (one JSON object per line: ts, level, topic, msg). stdout is not affected |
-h, --help |
Show help for the program or a command |
Example combining a global option with a command:
pegel --compact current BONN | jq '.value'
Exit codes: 0 success, 2 usage/parse errors (unknown command/option, missing
argument, invalid flag value, a station name that names several stations), 4 on a 404 from the API, 1 for any other
runtime/network error.