Busabase
Busabase is an open-source (MIT) database and workspace for AI agents — data, docs, skills, and apps in one place. Run it locally, self-host it inside your network, or use Busabase Cloud, and turn age
TypeScript API client and CLI for the open German federal budget API (bundeshaushalt.de)
DataTypeScript · JavaScriptGitHub @maschinenlesbar-orgOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
TypeScript API client & CLI for the open Bundeshaushalt budget-data API — the German federal budget.
Loading the full README. The saved excerpt is shown below.
Website: English · Deutsch — command reference, guides and API docs
Query the German federal budget from your terminal. bundeshaushalt is a
small command-line tool over the open
bundeshaushalt.de budget-data portal: fetch
expenses and income by year, drill into individual budget items, economic
groups or functional areas, and compare planned vs. realised figures — as
clean JSON you can pipe straight into jq.Read the full README ↗Website: English · Deutsch — command reference, guides and API docs
Query the German federal budget from your terminal. bundeshaushalt is a
small command-line tool over the open
bundeshaushalt.de budget-data portal: fetch
expenses and income by year, drill into individual budget items, economic
groups or functional areas, and compare planned vs. realised figures — as
clean JSON you can pipe straight into jq.
--compact for one-line/scripting.budget, expenses, and income (the last two are convenient shortcuts).--base-url https://user:[email protected]; the password is shown as *** in everything the CLI prints.Note. The tool calls an undocumented internal endpoint of the portal (
/internalapi/budgetData). It is not a published, stable public API and can change or disappear without notice. Treat it as best-effort.
Want to use this as a TypeScript library or understand how it's built? See DEVELOPING.md.
npm i -g @maschinenlesbar.org/bundeshaushalt-cli
This installs the bundeshaushalt command. Requires Node.js 22.12+.
Check it works:
bundeshaushalt --help
No setup needed — the endpoint requires no key. Your first query:
bundeshaushalt expenses 2024
The result is a JSON object. The top-level breakdown lives under children;
summary metadata is in meta. Pull out just the children with jq:
bundeshaushalt expenses 2024 | jq '.children[] | {id, label, value}'
Drill into one budget item by taking an id from those results:
bundeshaushalt budget 2024 expenses --id 090168301
budget <year> <account> [options] federal budget data for a year and account side
expenses <year> [options] shortcut for: budget <year> expenses
income <year> [options] shortcut for: budget <year> income
<year> is a four-digit year between 2012 and next year (inclusive): each summer the
portal publishes the draft budget (Regierungsentwurf) for the following year.
<account> is expenses or income.
These apply to budget, expenses, and income:
| Option | Values | Description |
|---|---|---|
--quota <quota> | target | actual | Planned (target, default) vs. realised (actual) figures |
--unit <unit> | single | function | group | Grouping — budget item (default), functional area, or economic group |
--id <id> | element id | Drill into one element; G- prefix for groups, F- for functions, no prefix for the budget structure. The prefix must match --unit (a mismatch is rejected) and sets it when --unit is omitted. Sent without surrounding whitespace; an id with an invisible or control character (a zero-width space, a soft hyphen) or a / is refused before any request |
A few recipes to get going — see Usage.md for the full, use-case-driven set.
# Top-level federal expenses for 2024
bundeshaushalt expenses 2024
# Realised (actual) expenses — compare with planned (target, the default)
bundeshaushalt expenses 2023 --quota actual
# Break expenses down by economic group (Gruppe)
bundeshaushalt budget 2024 expenses --unit group
# Break expenses down by functional area (Funktion)
bundeshaushalt budget 2024 expenses --unit function
# Drill into one budget item by id
bundeshaushalt budget 2024 expenses --id 090168301
# Drill into an economic group (G- prefix)
bundeshaushalt budget 2024 expenses --unit group --id G-5
# Look at a historical year
bundeshaushalt expenses 2015 --quota actual
Every command prints pretty JSON to stdout. Errors and diagnostics go to
stderr, so piping stdout into jq stays clean.
Each line on stderr is a log record: a timestamp (UTC), a level (ERROR, WARN,
INFO) and a topic, the program and the area it comes from (bundeshaushalt.cli for usage
errors, bundeshaushalt.api for the API's answers and the hint after them, a malformed one included, bundeshaushalt.http for
the connection, bundeshaushalt.output for a failed write to stdout, an answer too deeply nested or too
large to print). An error about an
answer names the request (GET <url>: the answer is not valid JSON.). Text in an answer is
never changed silently: bytes that are not valid in its charset and half characters (lone
surrogates) are replaced with U+FFFD, so the output stays readable for jq, and a WARN
record of bundeshaushalt.api says how many. By default it is written log4j style; --log-format jsonl writes one
JSON object per line instead. A record is always one line: a line break, a control
character or a bidi control in a message (a server's text, a value you typed) is written
as an escape (\n, \u001b, \u202e), so it can neither split a record nor forge
another one, nor steer the terminal; a message longer than 4000 characters is cut and
ends in … (N more characters):
2026-10-09T14:03:12.481Z WARN [bundeshaushalt.http] requests to mirror.example are sent unencrypted (http:, not https:)
2026-10-09T14:03:12.902Z ERROR [bundeshaushalt.api] HTTP 404 for GET https://bundeshaushalt.de/internalapi/budgetData?…
2026-10-09T14:03:12.903Z INFO [bundeshaushalt.api] with --quota actual, a 404 also means that year's realised figures are not published yet …
bundeshaushalt --log-format jsonl expenses 2026 --quota actual 2>log.jsonl # {"ts":"…","level":"ERROR","topic":"bundeshaushalt.api","msg":"HTTP 404 …"}
# Compare planned vs. realised headline totals for 2023
bundeshaushalt expenses 2023 --quota target --compact | jq '.detail.value'
bundeshaushalt expenses 2023 --quota actual --compact | jq '.detail.value'
# List all top-level budget items as a tab-separated table
bundeshaushalt expenses 2024 \
| jq -r '.children[] | "\(.id)\t\(.label)\t\(.value)"'
# How many children does a query return?
bundeshaushalt expenses 2024 | jq '.children | length'
Use --compact for single-line JSON in pipelines and logs:
bundeshaushalt --compact expenses 2024 | jq -c '.children[]'
--compact (and every global option) works before or after the command —
both bundeshaushalt --compact expenses 2024 and bundeshaushalt expenses 2024 --compact
do the same thing. An option that takes a value may be given only once: a repeat
(--id 14 --id 06) is a usage error rather than "the last one wins".
Exit codes make the CLI easy to use in scripts:
| Code | Meaning |
|---|---|
0 | success (also --help / --version) |
4 | not found (404): an unknown budget item id, a year the portal has no data for, or — with --quota actual — a year whose realised figures aren't published yet. Also an id the portal answers with an empty element instead of a 404 (--id G-1 on the expenses side, --id G-05): ERROR [bundeshaushalt.api] … no budget element "G-1": the API answered with an empty element. |
1 | any other error — including bad usage / invalid arguments |
A reader that stops early (bundeshaushalt expenses 2024 | head -c 100) is ordinary
use: the CLI exits 0 quietly. If stderr's reader is gone (2>&1 | true), a failed run
still exits with its own code. stdout redirected to a file that cannot take all of it (a
file-size limit, a full disk) is an error, never a cut-short file with exit 0: exit 1 and
an ERROR record of bundeshaushalt.output (Could not write to stdout: …).
command not found: bundeshaushalt — the global npm bin directory isn't
on your PATH. Add $(npm prefix -g)/bin to it (npm bin was removed in
npm 9), or run via
npx @maschinenlesbar.org/bundeshaushalt-cli ….4 / "not found" — the budget item id doesn't exist for the
requested year/account/unit combination. Re-fetch a fresh list to pick a
valid id. With --quota actual it also means the year's realised (Ist)
figures aren't published yet: they appear only once the year's accounts are
closed, months after it ends, so the current year has none. Use an earlier
year or --quota target.2012 through next year (the draft budget for next year appears each summer).
Out-of-range years are rejected before any network request is made; next
year before its draft is published gives exit 4.1 — connectivity, DNS, or a timeout. Try again,
or raise the limit with --timeout 60000. The client retries 429/503
responses automatically (default 2 retries).These apply to every command and may be given before or after it:
| Option | Description |
|---|---|
-V, --version | Print the version number |
-h, --help | Show help for the program or a command |
--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 [bundeshaushalt.http] …) or jsonl (one JSON object per line: ts, level, topic, msg). stdout is not affected |
--base-url <url> | API base URL (default https://bundeshaushalt.de). Credentials in it (https://user:[email protected]) are sent as HTTP Basic auth, for a mirror behind a login, and shown as *** in everything the CLI prints |
--timeout <ms> | Time limit per request in milliseconds, reading the whole response included (0 = no limit; default 30000) |
--user-agent <ua> | User-Agent header value |
--max-retries <n> | Retries for transient 429/503 responses and reset connections, 0..10 (default 2); each waits 200 ms × attempt, or longer when the server's Retry-After asks (up to 30 s; a longer one is not retried, and the error names the requested wait). Each retry logs one WARN record of bundeshaushalt.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) |
A base URL on plain http: to a host other than loopback (localhost, 127.0.0.0/8,
::1) works, but the CLI writes one warning record to stderr before the first request, e.g.
… WARN [bundeshaushalt.http] requests to mirror.example are sent unencrypted (http:, not https:), or
… WARN [bundeshaushalt.http] the base URL's credentials are sent unencrypted to mirror.example (http:, not https:)
when it carries a user:password@ (never printed). stdout and the exit code are unchanged.
This CLI is a client — it accesses data it does not own or redistribute. The upstream data is © its provider and licensed separately from this tool's code. See DATA_LICENSE.md.
Bundesministerium der Finanzen — public data without copyright protection on bundeshaushalt.de (amtliches Werk); free to reuse incl. commercially, source credit recommended. (The parallel BMF Datenportal / GovData channel is
dl-de/by-2-0.)
Dual-licensed — use it under either:
See LICENSING.md for details, and CONTRIBUTING.md for the contribution policy (this project does not accept external code contributions). Commercial enquiries: [email protected].