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 & CLI for the Deutsche Digitale Bibliothek (DDB) API — search across German cultural-heritage collections.
翻譯準備中
正在載入完整 README。 以下為已儲存的摘錄。
Website: English · Deutsch — command reference, guides and API docs
Search Germany's digitised cultural heritage from your terminal. ddb is a
command-line tool over the v2
Deutsche Digitale Bibliothek API:
tens of millions of objects from German archives, libraries, museums and
research institutions — searchable, faceted, and returned as clean JSON you can
pipe straight into jq.檢視完整 README ↗Website: English · Deutsch — command reference, guides and API docs
Search Germany's digitised cultural heritage from your terminal. ddb is a
command-line tool over the v2
Deutsche Digitale Bibliothek API:
tens of millions of objects from German archives, libraries, museums and
research institutions — searchable, faceted, and returned as clean JSON you can
pipe straight into jq.
view, edm (Europeana Data
Model), binaries list, hierarchy (parents/children), IIIF manifest and more.--compact for scripting,
-o <file> to write to disk; XML components (edm, source-record) stream out raw.Want to use this as a TypeScript library or understand how it's built? See DEVELOPING.md.
npm i -g @maschinenlesbar.org/deutsche-digitale-bibliothek-cli
This installs the ddb command. Requires Node.js 22.12+.
Check it works:
ddb version # prints the backend version, e.g. 7.5
The read routes (search, item, version) are public — there is nothing to
register and no key to pass. Just run the commands.
A 403 is therefore unexpected: it means --base-url points at an authenticated
endpoint, or the specific item component is access-restricted — not "you need a key".
# Search objects matching a keyword (10 results by default)
ddb search Goethe
# How many objects match in total?
ddb search Goethe | jq '.response.numFound'
# Narrow to images in Berlin, and count the type distribution
ddb search Goethe --filter 'place_fct:"Berlin"' --filter type_fct:mediatype_002 --facet objecttype_fct
# Grab an object id from a result, then fetch its detail
ddb search Goethe --fields id | jq -r '.response.docs[0].id'
ddb item TNPFDKO2VDGBZ72RWC6RKDNZYZQZP3XK
| Command | What it does |
|---|---|
search <query> | Full-text / faceted search over the Solr object index (GET /2/search/index/{collection}/{requestHandler}) |
item <id> | Fetch one component of an object by its 32-character id (--part selects the component) |
version | Print the DDB backend version — a quick connectivity check |
New to terms like facet, AIP, EDM, sector, Solr fq or the *_fct
fields? The Glossary decodes every one.
search options| Option | Meaning |
|---|---|
--rows <n> | Number of documents to return (Solr rows, default 10; at most 2147483647) |
--offset <n> | Offset of the first document (Solr start) — for paging (at most 2147483647) |
--sort <spec> | Solr sort, e.g. "score desc" (relevance) or "id asc" |
--fields <list> | Comma-separated fields to return (Solr fl), e.g. id,label |
--filter <fq> | Solr filter query — repeatable, e.g. type_fct:mediatype_002 |
--facet <field> | Return value counts for this facet field — repeatable, e.g. type_fct |
--facet-limit <n> | Cap the number of values returned per facet (needs --facet; Solr's default is 100, -1 = all, still sorted by count) |
--collection <name> | Solr collection (default search) |
--handler <name> | Solr request handler (default select). Only handlers that return Solr's standard response envelope (numFound, docs) are supported; any other answer fails (exit 1) with an error naming the handler |
Query strings and --filter use Solr syntax; pass '*:*' to match
everything. --filter restricts the set (repeat to AND; OR inside one fq like
'place_fct:("Berlin" OR "Dessau")'), while --facet only counts values. The query and
every --filter go out trimmed and in composed Unicode (NFC), the only form the index matches (a München pasted with a
combining umlaut found far fewer documents, as a filter none). A value that holds an invisible
or control character (a zero-width space, a soft hyphen, a bidi control) is refused before any
request, the character shown escaped (\u200b). Field names (--facet, --fields) and --sort
go out trimmed; a sort clause without asc or desc is refused.
When more documents match than were returned, ddb prints a short paging hint to
stderr (not for --rows 0, which asks for counts and facets only). An --offset at or
past the total gets a stderr note naming both instead (an INFO record of ddb.api: 754 documents match; --offset 100000 is past the end, so none are shown.); stdout keeps Solr's answer with an
empty docs and the exit code stays 0.
| Facet | Narrows by |
|---|---|
type_fct | Media type (mediatype_* codes; see the Glossary) |
objecttype_fct | Object type (Druckgraphik, …) |
place_fct | Place |
provider_fct | Contributing institution |
sector_fct | Cultural sector (sec_01..sec_07) |
language_fct | Language |
keywords_fct | Subject keywords |
mimetype_fct | MIME type of the media |
Facet counts come back under facet_counts.facet_fields.<field> as a flat
[value, count, …] array, which can include values with count 0. There is no
federal-state or time_fct facet (both fail with HTTP 500 "undefined field");
dates live in begin_time / end_time as day numbers, not years.
A few recipes to get going — see Usage.md for the full, use-case-driven set.
# Paging: documents 21–40 of a search
ddb search "Weimarer Republik" --rows 20 --offset 20
# Everything from one institution, id + label only, as compact JSON
ddb --compact search '*:*' --filter 'provider_fct:"Staatsarchiv München"' --fields id,label
# The Europeana Data Model record for one object (RDF/XML, straight to a file)
ddb item TNPFDKO2VDGBZ72RWC6RKDNZYZQZP3XK --part edm -o goethe.edm.xml
# Which places have the most matches for "Bauhaus"? (top of the flat facet array)
ddb search Bauhaus --rows 0 --facet place_fct --facet-limit 10 \
| jq '.facet_counts.facet_fields.place_fct'
search and JSON item components print JSON to stdout; version and the XML
components (edm, source-record) print raw text. Numbers are printed exactly or the answer
is an error (exit 1), never a rounded value; Solr's _version_ (a 64-bit document version
every search document carries, past what a JSON number keeps exactly) is printed as a string of
its digits, "_version_": "1858054135406919680". Errors and diagnostics
(including the paging hint) 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 (ddb.cli for usage
errors, ddb.api for the API's answers, a malformed one included, the paging notes and a WARN
when characters of an answer were not valid text and were replaced with U+FFFD, ddb.http for the
connection, ddb.output for -o and a failed write to stdout). 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 [ddb.http] requests to mirror.test are sent unencrypted (http:, not https:)
2026-10-09T14:03:12.902Z ERROR [ddb.api] HTTP 404 for GET https://api.deutsche-digitale-bibliothek.de/2/items/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/view: Not Found
ddb --log-format jsonl search Goethe 2>log.jsonl # {"ts":"…","level":"INFO","topic":"ddb.api","msg":"99866 documents match; …"}
# id + label for the current result page
ddb search Goethe | jq -r '.response.docs[] | "\(.id)\t\(.label)"'
# Total hits for a query
ddb search Goethe | jq '.response.numFound'
Use --compact for single-line JSON and -o <file> to write to a file — both
are global options that work before or after the command. An option that takes one
value (--rows, --sort, --base-url, …) may be given once; a second one is a usage
error (exit 2) rather than silently replacing the first. --filter and --facet
collect every value.
Exit codes make the CLI easy to use in scripts:
| Code | Meaning |
|---|---|
0 | Success (also --help / --version) |
2 | Bad usage / invalid argument, or an -o path that exists (without --force), is a directory or lies in a directory that is missing or not writable (nothing was sent) |
4 | Not found (404 from the API) |
6 | Network / transport failure (DNS, connection, timeout, size cap) |
1 | Any other runtime error (including an unexpected 403, or a 414 for a query or filter list too long for a URL) |
command not found: ddb — the global npm bin directory isn't on your
PATH. Run npm prefix -g to find the prefix and add its bin directory
("$(npm prefix -g)/bin"), or run via
npx @maschinenlesbar.org/deutsche-digitale-bibliothek-cli ….4 / "not found" — the id passed to item doesn't exist (or that
component isn't available for it — e.g. iiif/citation only exist for some
objects; a sub-item's source-record the API points at ancestors that have none,
which the message spells out). Re-fetch the id from a fresh search. Ids are exactly 32 upper-case
letters and digits; any other id is rejected up front (exit 2).docs array — the query matched nothing; broaden the keyword or relax
a --filter, and check response.numFound.403 — the read routes are public, so this usually means a custom
--base-url targets an authenticated endpoint, or the item component is
access-restricted.429/503 (and a reset connection) automatically up to
--max-retries times, each after a short linear backoff or the server's longer
Retry-After (a wait of more than 30 s is not retried: the error is reported at
once and names the wait the server asked for); if it persists, slow down.These may be given before or after the command, e.g.
ddb --compact search Goethe:
| Option | Description |
|---|---|
-V, --version | Print the CLI 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 [ddb.http] …) or jsonl (one JSON object per line: ts, level, topic, msg). stdout is not affected |
-o, --output <file> | Write output to this file instead of stdout (- = stdout; any other value that starts with a dash is refused as the next option, a usage error — write ./-name for such a file; refuses to overwrite an existing file (a dangling link too) unless --force, a directory always, and a file whose directory is missing, not a directory or not writable, checked before any request is made: a usage error, exit 2. The new file replaces the old one only once it is complete, so a write that fails part-way, a full disk, leaves the old file as it was and no half-written one; Ctrl-C or a SIGTERM/SIGHUP during the write leaves neither a half-written nor an empty file (exit 130, 143, 129). A device, a FIFO or a descriptor path (/dev/null, /dev/stdout, >(gzip > x.gz)) is written in place and needs no --force) |
--force | Overwrite the --output file if it already exists (needs --output); through a symlink it writes the file the link names, and creates it if the link dangles |
--base-url <url> | API base URL (default https://api.deutsche-digitale-bibliothek.de/2). A user:password@ in it is sent as Basic auth and never printed: every message, usage errors included, shows ***@, and echoed back by a server (the Basic value, user:password, the password) it is shown as ***; write a literal % in it as %25. A plain http: URL to a host other than loopback (localhost, 127.0.0.0/8, ::1) logs one warning per run on stderr (a record of : ), naming the credentials when the URL carries some (never their value); 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 governed separately from this tool's code. See DATA_LICENSE.md.
Deutsche Digitale Bibliothek — the API serves metadata exclusively under CC0 1.0 (no attribution required). The digital object media (not returned by this CLI) carry their own per-object rights — check each object's rights statement before reusing its media.
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].
WARNddb.http… sent unencrypted to <host> (http:, not https:)--timeout <ms> | Per-request timeout (default 30000; at most 2147483647) |
--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 the server's Retry-After, up to 30 s). Each retry logs one WARN record of ddb.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) |