Usage

Practical, use-case-driven examples for the lobbyregister CLI — a command-line client for the open German Lobbyregister search API (/sucheJson), the federal register of interest representatives (lobbyists) before the Bundestag and the federal government.

The CLI is read-only and needs no API key. It exposes two commands — search and count — and prints JSON to stdout, so it composes cleanly with jq.

Install

npm i -g @maschinenlesbar.org/lobbyregister-cli

This installs a single bin named lobbyregister. Confirm it works:

lobbyregister --version
lobbyregister --help

Without a global install you can also run it from a checkout with node apps/cli/dist/src/cli/index.js … (after npm run build).

Use cases

1. Count how many entries mention a topic

Gauge how strongly a sector is represented, as a single number.

lobbyregister count Energie

Prints an envelope with the query and the API-reported total:

{ "query": "Energie", "resultCount": 1234 }

count takes an optional query, --filter and the global options — no paging or sorting flags. It is not a cheaper request: the API has no count-only mode, so count downloads the same records as search (the whole register, about 18 MB, without a query or filter) and prints only resultCount. A loop over several topics (§7) downloads each topic’s full set. Should resultCount ever disagree with the number of records downloaded, count still prints resultCount and says so on stderr, in a WARN record: the register reports resultCount 2351 but returned 2350 results; the count shown is resultCount (the exit code stays 0).

2. Search lobbyists by keyword

Retrieve the full result set for a search term as the API returns it.

lobbyregister search Wasserstoff

Prints the full envelope: resultCount plus the results array of register entries (each a raw, schema-versioned RegisterEntry JSON object).

3. Get only the entries, not the envelope

When you want to pipe the records straight into another tool and don’t care about the surrounding resultCount wrapper.

lobbyregister search Wasserstoff --results-only

--results-only prints just the results array instead of the full envelope.

4. Sort results by newest registration first

See which interest representatives most recently registered for a topic.

lobbyregister search Pharma --sort REGISTRATION_DESC

--sort values (the orders of the register’s website search): RELEVANCE_DESC (the default with a query), REGISTRATION_DESC (first published), UPDATE_DESC (last updated), INACTIVITY_DESC (went inactive), NAME_ASC, FINANCIALEXPENSES_DESC (declared spend), DONATIONAMOUNT_DESC, MEMBERSHIPFEES_DESC, NUMBEROFREGULATORYPROJECTS_DESC, NUMBEROFSTATEMENTS_DESC, NUMBEROFFTE_DESC, NUMBEROFENTRUSTEDPERSONS_DESC, NUMBEROFCONTRACTS_DESC, NUMBEROFMEMBERS_DESC, NUMBEROFMEMBERSHIPS_DESC — each also in the other direction (_ASC / _DESC). The value is passed through verbatim and is case-sensitive. The API ignores an unrecognised value (HTTP 200, default ordering), so a bad sort never raises a 400; the CLI compares the order the API reports (searchParameters.sortOrder) with the one you asked for and logs a WARN record on stderr when they differ (exit 0). The record quotes the reported order cleaned, as it does an error message: control characters dropped, on one line, at most 200 characters.

# Biggest declared lobbying budgets first
lobbyregister search Energie --sort FINANCIALEXPENSES_DESC --page-size 10 --results-only

5. Page through a large result set

Browse results in fixed-size chunks instead of dumping everything at once. The live endpoint ignores paging parameters and returns all matches, so paging is applied client-side: every run downloads the full set and the client slices the results array (nothing paging-related is sent). --page is 1-based and requires --page-size; both must be 1 or more (0 is a usage error, exit 2).

# First 10 entries
lobbyregister search Digitalisierung --sort REGISTRATION_DESC --page-size 10 --page 1

# Next 10 entries
lobbyregister search Digitalisierung --sort REGISTRATION_DESC --page-size 10 --page 2

resultCount still reflects the true total; only the visible slice is trimmed.

Each run downloads the set again, and the register’s relevance order (RELEVANCE_DESC, the default with a query) is not stable between requests: two identical searches return the entries in a different order, so page 1 and page 2 from separate runs can overlap or skip entries. Page with a date sort (REGISTRATION_DESC, stable in our checks), or fetch the whole set once and slice it yourself. When it pages a relevance-ordered set, the CLI logs an INFO record on stderr (exit 0).

6. Extract just the names of matching organisations with jq

Turn raw register entries into a flat list for a report or spreadsheet.

lobbyregister search Klimaschutz --results-only \
  | jq -r '.[].lobbyistIdentity.name // empty'

--results-only gives jq a plain array to iterate. (Adjust the field path to the entry shape in your results; jq '.[0] | keys' reveals the available keys.)

7. Compare topic coverage with a one-liner

Pull just the count for several topics to compare their footprint in the register.

for topic in Energie Verkehr Gesundheit Landwirtschaft; do
  printf '%s\t' "$topic"
  lobbyregister count "$topic" | jq '.resultCount'
done

8. List the newest registrations in a topic as a compact table

Combine sorting, paging and jq to skim recent entrants.

lobbyregister search Rüstung --sort REGISTRATION_DESC --page-size 5 --results-only \
  | jq -r '.[] | [.registerNumber, .lobbyistIdentity.name] | @tsv'

9. Stream single-line JSON into a logging or ETL pipeline

--compact prints JSON on one line, which is friendlier for line-oriented tools.

lobbyregister search Chemie --results-only --compact \
  | jq -c '.[] | {nr: .registerNumber}'

10. Narrow a search with the register’s own filters

--filter <attribute=value> passes the facet filters of the register’s website search to the API, on search and count. It can be repeated: values of one attribute are alternatives (OR), different attributes must all match (AND).

# Entries with revolving-door data: a lobbyist, legal representative, entrusted
# person or contractor who recently held public office
lobbyregister count --filter revolvingdoordata=true

# ... among the energy entries, only where it concerns an entrusted person
lobbyregister search Energie --filter revolvingdoordata=true \
  --filter revolvingdoorpersontypes=ENTRUSTED_PERSON --results-only

# Inactive entries with the field of interest "Energie"
lobbyregister search --filter activelobbyist=false --filter fieldsofinterest=FOI_ENERGY
# Entries that declared donations received (the code, not "true")
lobbyregister count --filter donationsreceived=DONATIONS_RECEIVED

# A field-of-interest code as it appears in the entries' fieldsOfInterest[].code
lobbyregister count --filter fieldsofinterest=FOI_EU_LAWS
# -> sent as fieldsofinterest=FOI_EUROPEAN_UNION|FOI_EU_LAWS, the form the register matches

lobbyregister search --help lists the attributes. The values are the register’s codes (true/false for yes/no facets; FOI_ENERGY, DONATIONS_RECEIVED, ACT_TRADE_ASSOC and so on otherwise), matched case-insensitively; a sub-field is FOI_WORK|FOI_WORK_POLICY, and a bare sub-field code from the data (FOI_WORK_POLICY) is sent in that form. An unknown attribute is a usage error (exit 2) because the API would silently ignore it and return the whole set; an unknown value is a usage error too, naming the valid codes, because the API would match nothing (resultCount: 0) and “nobody” would look like an answer. --allow-unknown-filters sends either anyway (for a code the register added after this release); a reply of 0 then logs an INFO record on stderr. The library exports the codes as SEARCH_FILTER_VALUES. If a reply does not confirm a filter in searchParameters.facets, the CLI exits 1 rather than print an unfiltered set. The website’s number ranges (spend, staff, members from/to) are not supported.

11. Search a term that begins with a dash

A leading - would otherwise be parsed as an option. End the options with --.

lobbyregister search -- -Energie

This searches for the literal term -Energie.

12. What the query understands (observed, not documented)

The register publishes no query syntax. What follows is what its search answered on 2026-10-05 (live, search/count, register of 6 996 entries); it is observed behaviour, not a promise, and may change without notice. Check a surprising count before relying on it.

Query Observed Reading
Tabak 66 one word: the plain search
R000534, r000534 that entry a register number, either case
R000534 R002822 0 (each alone: 1) words separated by spaces must all match
Tabak AND Wasserstoff 3 AND (capitals) — all terms
Tabak OR Wasserstoff 480 OR (capitals) — any term
'"Deutscher Bauernverband"' 75 a phrase in double quotes is accepted (not compared with the unquoted words)
* 6 996 the whole register
Taba* 3 not a prefix search: fewer than Tabak alone — don’t rely on * inside a word
-Tabak (after --) 66, same as Tabak a leading - is not NOT
Tabak: 66, same as Tabak a trailing colon is ignored
"Tabak, Tabak (, ~2 000 characters exit 1 rejected by the register (redirect to its /fehler page; see the README’s troubleshooting)

Quote the whole query for the shell, as above: lobbyregister count 'Tabak OR Wasserstoff'.

Global options

These apply to every command and may be given before or after the command name (e.g. both lobbyregister --compact count Energie and lobbyregister count Energie --compact work):

Option Description
-V, --version Print the version and exit
--base-url <url> API base URL (default https://www.lobbyregister.bundestag.de); http(s) only, given once. A user name or password in it is a usage error (exit 2; the API needs no credentials, and the message never repeats them). A plain http: URL to a host other than loopback (localhost, 127.0.0.0/8, ::1) logs one WARN record of lobbyregister.http (requests to <host> are sent unencrypted (http:, not https:)) on stderr before the first request; stdout and the exit code are unchanged
--timeout <ms> Per-request timeout in milliseconds (default 30000)
--user-agent <ua> User-Agent header value (default lobbyregister-cli; not blank, no control characters, Latin-1 only)
--max-retries <n> Retries for transient 429/503 responses and reset connections (default 2); each waits 200 ms × attempt, or the server’s Retry-After when that is longer (up to 60 s). A timeout is not retried. Each retry logs one WARN record of lobbyregister.http before it waits (HTTP 503 from host: retry 1 of 3 in 2 s)
--max-redirects <n> HTTP redirects to follow (0 = none; default 5)
--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 [lobbyregister.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

Exit codes: 0 success, 2 usage error, 4 on a 404 from the API, 1 for any other error (network, parse, or other non-404 HTTP status).

View the source on GitHub →