@maschinenlesbar.org/dip-bundestag-cli
v0.0.3
Published
TypeScript API client and CLI for the Bundestag DIP API (search.dip.bundestag.de)
Downloads
388
Maintainers
Readme
dip-bundestag-cli
Browse Germany's Bundestag parliamentary record from your terminal. dip is
a command-line tool over the
Bundestag DIP API
(search.dip.bundestag.de/api/v1): search procedures, printed papers, plenary
protocols, activities and people — as clean JSON you can pipe straight into
jq.
- All eight DIP resources in one command — Vorgänge, Drucksachen,
Plenarprotokolle and more, each with
listandget. - Clean JSON output — pretty-printed by default,
--compactfor one-line/scripting,-o <file>to write directly to disk. - Cursor pagination built in — pass the returned
cursorback via--cursorto walk large result sets. - Flexible filtering — pass any DIP
f.*filter verbatim via--filter key=value;--idis shorthand for the repeatablef.idfilter.
Want to use this as a TypeScript library or understand how it's built? See DEVELOPING.md.
Install
npm i -g @maschinenlesbar.org/dip-bundestag-cliThis installs the dip command. Requires Node.js 20+.
Check it works:
dip --helpAPI key
DIP requires an API key — it is not bundled. Request a personal key free of
charge from [email protected], then export it:
export DIP_API_KEY=your-personal-keyOr pass it per-invocation (it is a global option, so it works before or after the command):
dip --api-key your-personal-key vorgang listPrecedence is --api-key > DIP_API_KEY env var > built-in default (which is
expired — without your own key, requests return 401). On a 401 the CLI
prints a plain-language hint with the address to request a key.
Quickstart
export DIP_API_KEY=your-personal-key
# Procedures matching a title keyword
dip vorgang list --filter f.titel=Klimaschutz
# How many matched?
dip vorgang list --filter f.titel=Klimaschutz | jq '.numFound'
# Fetch one procedure by id
dip vorgang get 282486
# Full text of a printed paper
dip drucksache-text list --filter f.titel=Haushaltsgesetz --filter f.wahlperiode=20 \
| jq -r '.documents[0].text'Commands
Every resource follows the same two-subcommand pattern:
<resource> list [--cursor <c>] [--id <id> …] [--filter key=value …]
<resource> get <id>| Resource | What it is |
| --- | --- |
| vorgang | Procedure / legislative process |
| vorgangsposition | Step within a procedure |
| drucksache | Printed paper (metadata only) |
| drucksache-text | Printed paper with extracted full text |
| plenarprotokoll | Plenary protocol (metadata only) |
| plenarprotokoll-text | Plenary protocol with extracted full text |
| aktivitaet | Activity — links a person to a procedure |
| person | Person (member / actor) |
New to terms like Vorgang, Drucksache, Wahlperiode or Vorgangstyp? The Glossary decodes every one.
list options
| Option | Meaning |
| --- | --- |
| --cursor <cursor> | Pagination cursor from a previous page |
| --id <id> | Filter by id — repeatable; maps to f.id |
| --filter <key=value> | Raw DIP filter, e.g. f.titel=Klima — repeatable |
--filter passes the key and value verbatim to DIP. Only the first = splits
key from value, so a value may itself contain =. Repeating the same key sends
repeated query parameters, which DIP treats as an OR set. --id and
--filter f.id=… are merged (neither silently wins).
Common DIP filters
| Filter | Meaning |
| --- | --- |
| f.titel=<text> | Title keyword |
| f.id=<n> | Specific document id |
| f.wahlperiode=<n> | Electoral term (e.g. 20) |
| f.datum.start=<YYYY-MM-DD> | Date range start |
| f.datum.end=<YYYY-MM-DD> | Date range end |
| f.aktualisiert.start=<YYYY-MM-DDThh:mm:ss> | Last-updated range start (full ISO datetime) |
| f.aktualisiert.end=<YYYY-MM-DDThh:mm:ss> | Last-updated range end |
| f.vorgangstyp=<type> | Procedure type (e.g. Gesetzgebung) |
| f.dokumentart=<type> | Document type |
| f.zuordnung=BT\|BR | Chamber — Bundestag (BT) or Bundesrat (BR) |
| f.person=<name> | Person surname (for the person resource) |
Common tasks
A few recipes to get going — see Usage.md for the full, use-case-driven set.
# Procedures by date range
dip vorgang list \
--filter f.datum.start=2024-01-01 \
--filter f.datum.end=2024-03-31
# Drucksachen for one electoral term, Bundestag only
dip drucksache list --filter f.wahlperiode=20 --filter f.zuordnung=BT
# Look up a person, then fetch their full record
dip person list --filter f.person=Merkel \
| jq -r '.documents[] | "\(.id)\t\(.titel)"'
dip person get 7240
# Plenary protocol transcript to a file
dip plenarprotokoll-text get 5678 | jq -r '.text' > protokoll.txt
# Activities updated since a date (full ISO datetime required)
dip --output aktivitaeten.json aktivitaet list \
--filter f.aktualisiert.start=2024-05-01T00:00:00 --filter f.wahlperiode=20Output & scripting
Every command prints pretty JSON to stdout. Errors and diagnostics go to
stderr, so piping stdout into jq stays clean.
# Total result count for a query
dip vorgang list --filter f.titel=Klimaschutz | jq '.numFound'
# Extract titles from the current page
dip drucksache list --filter f.titel=Klimaschutz \
| jq -r '.documents[].titel'
# Cursor pagination — walk page by page
CURSOR=$(dip vorgang list --filter f.wahlperiode=20 | jq -r '.cursor')
dip vorgang list --filter f.wahlperiode=20 --cursor "$CURSOR"
# Fetch several documents by id in one call
dip drucksache list --id 123456 --id 123457 --id 123458 \
| jq -r '.documents[] | "\(.id)\t\(.titel)"'Use --compact for single-line JSON. Note that --compact is a global
option — it works before or after the command:
dip --compact vorgang list --filter f.titel=Klimaschutz | jq -c '.documents[]'Use -o <file> to write output to a file instead of stdout (also a global
option — works before or after the command):
dip --output results.json drucksache list --filter f.titel=BürgergeldExit codes make the CLI easy to use in scripts:
| Code | Meaning |
| --- | --- |
| 0 | Success (also --help / --version) |
| 2 | Bad usage / invalid argument (nothing was sent) |
| 4 | Document not found (404 from the API) |
| 1 | Any other runtime error — including 401 (missing/expired key) and network failures |
Troubleshooting
command not found: dip— the global npm bin directory isn't on yourPATH. Runnpm bin -gto find it and add it, or run vianpx @maschinenlesbar.org/dip-bundestag-cli ….- Exit
1/ "Authentication failed (401)" — no key was sent, or the key has expired. ExportDIP_API_KEYor pass--api-key. Request a personal key from[email protected]. - Exit
4/ "not found" — the id passed togetdoesn't exist. Re-fetch it from a freshlistresult; ids can change as the catalogue updates. - Exit
1/ rate-limited — the shared key (if used) is rate-limited; the client retries429/503automatically up to--max-retriestimes. If the error persists, use a personal key or increase--timeout. - Exit
1/ network error — connectivity, DNS, or a timeout. Try again or raise the limit with--timeout 60000. - Empty
documentsarray — the query matched nothing; try a broader keyword, remove filters, or check thenumFoundfield. 400 Invalid date-time—f.aktualisiert.start/f.aktualisiert.endrequire a full ISO datetime (YYYY-MM-DDThh:mm:ss), not a bare date. Usef.datum.start/f.datum.endfor plainYYYY-MM-DDdates.
Global options
These may be given before or after the command, e.g.
dip --api-key $DIP_API_KEY vorgang list:
| Option | Description |
| --- | --- |
| -V, --version | Print the version number |
| -h, --help | Show help for the program or a command |
| --api-key <key> | DIP API key (env DIP_API_KEY) |
| --compact | Print JSON on a single line instead of pretty-printed |
| -o, --output <file> | Write output to this file instead of stdout |
| --base-url <url> | API base URL (default https://search.dip.bundestag.de) |
| --timeout <ms> | Per-request timeout (default 30000) |
| --user-agent <ua> | User-Agent header value |
| --max-retries <n> | Retries for transient 429/503 responses (default 2) |
| --max-response-bytes <n> | Cap response body size in bytes (0 = unlimited; default 100 MiB) |
Learn more
- SKILLS.md — Claude Code Agent Skills that drive this CLI.
- Usage.md — full use-case-driven cookbook.
- GLOSSARY.md — every domain term and filter explained.
- DEVELOPING.md — TypeScript library usage, architecture, testing, CI.
License
Dual-licensed — use it under either:
- AGPL-3.0-or-later (default, free). Note the AGPL's §13 network clause: if you run a modified version as a network service, you must offer that modified source to the service's users.
- Commercial license (paid), for closed-source / proprietary or SaaS use without the AGPL's obligations.
See LICENSING.md for details, and CONTRIBUTING.md for the contribution policy (this project does not accept external code contributions). Commercial enquiries: [email protected].
