Usage

ladesaeulen — a CLI for the Bundesnetzagentur Ladesäulenregister. No API key needed. The register lists the stations whose operators have completed the BNetzA’s notification procedure, so counts are “listed in the register”, not every public charger in Germany.

ladesaeulen [global options] <command> [command options]

Global options

Option Description
--base-url <url> API base URL (the ArcGIS FeatureServer); http:/https: only, no query (?), fragment (#) or surrounding whitespace, and a % in a user name or password must be an escape (write a literal % as %25); anything else is a usage error (exit 2) before any request. A plain http: URL to a non-loopback host (not localhost, 127.0.0.0/8, ::1) gets one WARN record of ladesaeulen.http (… sent unencrypted (http:, not https:)) on stderr naming the host (and the URL’s credentials, never printed); stdout and the exit code are unchanged. A user:password@ in it is sent as Basic auth and shown as ***@ in every message; echoed back by a server (the Basic value, user:password, the password), it is shown as ***
--timeout <ms> time limit per request in ms, whole response included (default 30000 = 30 s; 0 = no timeout; at most 2147483647)
--user-agent <ua> User-Agent header value (not blank; Latin-1 text without control characters)
--max-retries <n> retries for transient 429/503 responses and reset connections (0..10, default 2). Each retry backs off 200 ms, 400 ms, …, or waits the server’s Retry-After (seconds or an HTTP-date) when that is longer; a Retry-After above 30 s is not retried — the error is reported at once and names the wait the server asked for. A connection reset mid-request (socket hang up, ECONNRESET, a body cut off mid-way) is retried the same way, with the linear backoff, for a GET (a long --where sent as a form POST is not re-sent); a run that recovers exits 0. A status or reset that persists ends the message with (after N retries). Other network errors (a refused connection, DNS, a timeout) are not retried. Each retry logs one WARN record of ladesaeulen.http before it waits (HTTP 503 from host: retry 1 of 3 in 2 s)
--max-response-bytes <n> cap the response body size in bytes (0 = unlimited; default 100 MiB)
--compact print JSON on a single line (for piping to jq)
--log-format <format> how errors, warnings and notes are written to stderr: text (default; log4j style, 2026-10-09T14:03:12.481Z WARN [ladesaeulen.http] …) or jsonl (one JSON object per line: ts, level, topic, msg). stdout is not affected
-V, --version / -h, --help version / help

Commands

stations — search charging stations

Option Description
--where <sql> SQL filter, e.g. "Ort='Berlin' AND Typ='Schnellladeeinrichtung'"
--limit <n> max rows per request (1..10000, default 50). The server returns at most ~2000 — page the rest with --offset
--offset <n> rows to skip (paging)
--order-by <spec> sort, e.g. "Ort ASC" or "CAST(max_electric_power_station AS FLOAT) DESC" (power is a text column; without the cast it sorts as text)
--fields <list> comma-separated field list, or '*' for all
--near <lat,lon> only stations near this WGS84 point (needs --radius)
--radius <km> search radius in km for --near: a plain decimal from 0.001 (1 m) to 1000
--min-point-kw <kW> only stations where one charge point can deliver at least this many kW (a plain decimal above 0, at most 10000). Read from the connector ratings in Steckersystem_Ladepunkt1..10, capped at the station figure; each row gets the derived max_charge_point_kw, and the connector columns are added to --fields. The server filters on the station figure and the CLI on the ratings, so a page can be shorter than --limit; with --count the CLI reads every candidate, one request per 2,000
--count print only the number of matching stations (all of them: combine it with --where and --near/--radius; with --limit, --offset, --order-by, --fields or --geojson it is a usage error)
--geojson output a GeoJSON FeatureCollection instead of ArcGIS JSON

Default output is { features, exceededTransferLimit } — exceededTransferLimit: true means more matched than were returned (the server caps a page at ~2000 rows). The CLI prints a stderr note in that case, saying whether your --limit or the server’s cap cut the page; page with --offset, raise --limit or narrow --where. --geojson gets the same note: there the server puts the flag in the FeatureCollection’s properties.exceededTransferLimit. A reply that is not a FeatureCollection with a features array (a gateway’s {}, features: null) is an error (exit 1), not an empty map.

count-by <field> — grouped counts

ladesaeulen count-by state → [{ value, count }, …], sorted by count desc. Add --where to aggregate a subset. It groups by one field: a comma-separated list (state,Typ) is a usage error — group by one field and fix the other with --where (count-by state --where "Typ='Schnellladeeinrichtung'"). The field is one column name as fields lists it (letters, digits and _); anything else — a line break, a space, an expression such as UPPER(Ort) — is a usage error (exit 2) before any request. Good fields: state, Typ, operator_companyName, Ort (Betreiber is null on more than half of the stations). The result stops at 2,000 groups (the server’s page limit; the CLI then prints a stderr note); the top groups are still correct because the server sorts by count first.

info — how current the data is

ladesaeulen info → { name, lastEditDate, dataLastEditDate, maxRecordCount }, the layer’s own metadata; the dates are ISO 8601 UTC ("2026-10-01T13:53:22.139Z"). The register is a snapshot, refreshed irregularly, and no row carries an as-of date (documentDate is always empty), so cite dataLastEditDate with an answer.

fields — list queryable columns

ladesaeulen fields → [{ name, type, alias }, …]. Use it to build --where, --fields and count-by. Sixteen of the listed columns are null on every row (the evses_* per-connector columns, documentDate, documentTime, json_type, Steckersystem_Ladepunkt7..10; see GLOSSARY.md): fields names them on stderr, and stations/count-by log an INFO record (… is empty on every row) when a filter, sort, field list or group names one, since its 0 or single null group is not an answer.

The --where filter

Standard Esri SQL over the layer’s columns:

Common fields: Ort, Postleitzahl, state, Betreiber, operator_companyName, Typ (Normalladeeinrichtung/Schnellladeeinrichtung), Status, max_electric_power_station, Anzahl_Ladepunkte.

Examples

ladesaeulen stations --count                                        # 116343 on 2026-09-15
ladesaeulen stations --where "state='Berlin'" --count
ladesaeulen count-by Typ --compact
ladesaeulen stations --near 52.5163,13.3777 --radius 2 --where "Typ='Schnellladeeinrichtung'" --geojson
ladesaeulen fields --compact | jq '.[].name'

Exit codes

Code Meaning
0 success (help/version included); an empty result also exits 0
1 API/logical error (the ArcGIS error envelope), or a catch-all
2 usage error (bad flags, unknown command, --near without --radius, --count with a paging/field option, a non-http(s) or malformed --base-url, redirecting base URL)
4 HTTP 404
6 network / transport failure (DNS, connection, timeout, response size-cap; a reset connection once the retries are spent)

A reader that stops early (ladesaeulen count-by Ort | head -n 3) ends the run quietly with 0. When stderr’s reader is gone (2>&1 | true), a failed run still exits with its own code.

Notes

View the source on GitHub →