Busabase
Busabase는 AI 에이전트를 위한 오픈소스(MIT) 데이터베이스 및 작업 공간입니다
TypeScript API 클라이언트·CLI로 독일 연방네트워크청 전·가스 시장 단위 등록부(MaStR)를 이용
TypeScript API client and CLI for the Marktstammdatenregister (MaStR) — the Bundesnetzagentur's register of German electricity & gas market units
데이터TypeScript · JavaScriptGitHub @maschinenlesbar-org온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
TypeScript API client & CLI for the Marktstammdatenregister (MaStR) — the Bundesnetzagentur register of German electricity and gas market units.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Website: English · Deutsch — command reference, guides and API docs A dependency-light TypeScript client + CLI for the Marktstammdatenregister (MaStR) — the Bundesnetzagentur's register of the German electricity & gas market: ~9 million generation and consumption units (solar, wind, storage, CHP, …). Wraps the public "erweiterte öffentliche Einheitensuche". A bund.dev API. • No API key. The public search is open. • Zero runtime HTTP dependencies. Built on node:http/https; the CLI's only runtime dependency is commander. • Library + CLI. Use the typed MastrClient, or the mastr command.README 전문 보기 ↗
Website: English · Deutsch — command reference, guides and API docs
A dependency-light TypeScript client + CLI for the Marktstammdatenregister (MaStR) — the Bundesnetzagentur's register of the German electricity & gas market: ~9 million generation and consumption units (solar, wind, storage, CHP, …). Wraps the public "erweiterte öffentliche Einheitensuche". A bund.dev API.
node:http/https; the CLI's only
runtime dependency is commander.MastrClient, or the mastr command.We provide the tool, not the data. MaStR data is © the Bundesnetzagentur under DL-DE-BY-2.0 — free to use with attribution. See DATA_LICENSE.md.
npm install -g @maschinenlesbar.org/marktstammdatenregister-cli # the `mastr` command
# or as a library:
npm install @maschinenlesbar.org/marktstammdatenregister-cli
Requires Node.js 22.12+.
Four datasets, each paged and filterable. Prints { total, data } (or just the
count with --total).
mastr stromerzeugung --total # how many electricity-generation units? → 9562366 (2026-10-05; it grows)
mastr stromerzeugung --filter "Energieträger~eq~'2495'" --total # …how many are solar? → 6545851
mastr stromerzeugung --sort "Bruttoleistung-desc" --page-size 20 --iso-dates
mastr gaserzeugung --page 1 --page-size 50
mastr filters stromerzeugung # the filterable columns + dropdown codes
Categories: stromerzeugung, stromverbrauch, gaserzeugung, gasverbrauch.
Filtering uses the register's own syntax — FilterName~op~'value'~and~…
(ops eq|neq|sw|ct|nct|ew|null|nn, plus the strict gt/lt — meant for numbers and dates,
but the register applies them to text and dropdown columns too, comparing the text or the code);
there is no working ~or~ (the register drops everything after it, so the CLI
rejects it) — for several codes of one dropdown, list them in one value:
Energieträger~eq~'2497,2498'. Discover the German field names and dropdown codes with mastr filters <category>.
The register silently ignores a FilterName it doesn't know (you'd get the unfiltered set)
and answers an unknown dropdown code with 0 rows, so the CLI checks every FilterName and
every eq/neq dropdown code against the category's columns (one extra request) and
rejects an unknown one (exit 2, with "did you mean"); a name typed with a decomposed umlaut,
padding or in another case is sent the register's way. A value is sent as text: trimmed
inside its quotes and in NFC (the register trims codes, not text: ' Münster ' matched
nothing), and one with an invisible, control or half character is rejected. A malformed spec
or an unknown operator (e.g. gte) is rejected too. Several --filter flags are joined with ~and~;
any other option given twice is a usage error. --sort takes FieldKey-asc or
FieldKey-desc (several joined by ~), sent trimmed; any other form (no direction, -down,
a name with spaces) is a usage error before any request, since the register sorted it ascending
or not at all without a word. A wrong --sort field name is refused by the
register (live 2026-10-09: {"Error":true}, exit 1 with an ERROR record of mastr.api
that names the sort key; it used to answer 0 rows, so sanity-check a sorted query with
--total); sort keys are record field names (Bruttoleistung), not FilterNames — list them with
mastr stromerzeugung --page-size 1 --compact | jq '.data[0] | keys'. Dates come as Microsoft
/Date(ms)/ strings — add --iso-dates to convert them to ISO-8601: date-only fields
(InbetriebnahmeDatum, …) as YYYY-MM-DD, timestamps (DatumLetzteAktualisierung) in German
time with an explicit offset, 2020-02-20T17:28:35.250+01:00.
Global flags: --base-url, --timeout, --user-agent, --max-retries,
--max-response-bytes, --log-format, --compact, --iso-dates. See Usage.md. A --base-url
on plain http: to a host other than loopback logs one WARN record of mastr.http
(… sent unencrypted …) on stderr before the first request (stdout and the exit code are
unchanged); the library's check is cleartextProblem(baseUrl).
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 (mastr.cli for usage errors,
mastr.api for the register's answers, a malformed one included, and the notes about them — also a WARN when characters of an answer that were not valid text (invalid bytes, a lone surrogate) were replaced with U+FFFD, so stdout stays well-formed JSON —, mastr.http for the
connection, mastr.output for a failed write to stdout and an answer that can't be printed — nested too deeply or too large). 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 [mastr.http] requests to mirror.test are sent unencrypted (http:, not https:)
2026-10-09T14:03:12.902Z INFO [mastr.api] 0 results with --filter set. If you expected matches, check the values: …
mastr --log-format jsonl stromerzeugung --filter "Ort~eq~'Nirgendwo'" --total 2>log.jsonl # {"ts":"…","level":"INFO","topic":"mastr.api","msg":"0 results with --filter set. …"}
import { MastrClient, parseMsDate } from "@maschinenlesbar.org/marktstammdatenregister-cli";
const mastr = new MastrClient();
const solar = await mastr.stromerzeugung({ filter: "Energieträger~eq~'2495'", pageSize: 10 });
solar.total; // total matching units
await mastr.count("stromerzeugung", { filter: "Energieträger~eq~'2495'" }); // just the count, one-row request
parseMsDate(String(solar.data[0]?.EinheitRegistrierungsdatum)); // Date | null
The client checks a filter's FilterNames and dropdown codes against the category's columns
(filterColumns(), fetched once per client and category) and throws MastrValidationError
for one the register would ignore; new MastrClient({ allowUnknownFilters: true }) skips that
check and its request. A query key it doesn't know (filtr) is refused the same way.
Build filters from user input with buildFilter, not string interpolation: a filter
value cannot contain ~ (the register splits on it and has no escape), so
`Ort~eq~'${input}'` lets an input like Münster'~and~Energieträger~eq~'2497 add a
condition. buildFilter quotes each value, sends a text value trimmed and in Unicode NFC
(normalizeText), and throws MastrValidationError for a ~ or a blank value:
import { buildFilter } from "@maschinenlesbar.org/marktstammdatenregister-cli";
const filter = buildFilter([
{ name: "Ort", op: "eq", value: town }, // quoted, ~ refused
{ name: "Energieträger", op: "eq", value: ["2497", "2498"] }, // comma list = any of
]);
await mastr.stromerzeugung({ filter, pageSize: 1 });
Code is dual-licensed AGPL-3.0-or-later OR commercial — see LICENSING.md. No external code contributions are accepted (see CONTRIBUTING.md); bug reports and AGPL forks are welcome.