Tutorial: build a resilient CLI for a click-first supplier portal
How hamberger-dl replaces repeated portal clicks with a Bash CLI: Keycloak OIDC with PKCE, protected token caching, filtered REST retrieval, safe PDF re-runs, and ZIP exports.
Open public reference →
hamberger-dl is an open-source command-line tool for downloading invoices from the Hamberger customer portal. It began with an ordinary operations problem: a portal optimised for manual clicks made it slow to list, filter, and download a month of supplier PDFs. Browser automation would be fragile. The better boundary was the portal's own API.
Start with the smallest useful operator workflow
The CLI has four operator verbs: login, list, download, and zip. An operator signs in once, lists invoices as plain text, filters by date or document type, downloads selected PDFs, or asks the portal for a ZIP. Keeping the interface small means the output can be used directly or passed safely into an accounting workflow.
The public interface supports --after and --before dates, RECHNUNG and GUTSCHRIFT document types, explicit document IDs, an output directory, and an overwrite flag. Filters run against the invoice metadata before download, so a command can target one credit note, one month, or a whole year without a browser session.
Screenshot: operator flow
Add a terminal screenshot here with synthetic IDs only. Show login → list with a date filter → download or zip. Redact account details, bearer tokens, paths that identify a user, and real invoice metadata.
Use the portal API, not browser automation
The tool talks to the portal's REST endpoints after authenticating with its Keycloak OpenID Connect flow. This avoids selectors, browser timing, and stored web sessions. The invoice listing is normalised to four operator-visible fields: date, type, gross amount, and document ID. The download and ZIP commands then use the selected IDs directly.
Implement OIDC as a CLI security boundary
Interactive login uses the authorisation-code flow with PKCE S256. The CLI creates a fresh verifier, derives the SHA-256 challenge, supplies state and nonce values, submits the portal login form, and exchanges the returned authorisation code for tokens. The password is entered interactively and is never written to disk.
The token cache is deliberately separate from the command output. It stores access and refresh tokens under the user's configuration directory; the directory has mode 700 and the token file has mode 600. A still-valid access token is reused. An expired access token first attempts a refresh-token grant. Only a rejected refresh returns the operator to interactive login.
Screenshot: authentication lifecycle
Add a redrawn sequence diagram here: PKCE login → token cache → valid access token or refresh grant → API call → interactive login only when refresh fails. Do not show client identifiers, token values, cookies, redirect parameters, or actual endpoints.
Make downloads safe to repeat
Each PDF is named from the portal document ID. Before downloading, the CLI checks whether that exact file already exists and skips it unless the operator passes --overwrite. An interrupted batch can therefore resume by running the same command again. The file check is the idempotency key; it is more useful than a vague 'download succeeded' message.
The ZIP path uses the same selection logic but sends the chosen IDs as a JSON payload to the portal package endpoint. This keeps a large export inside the upstream system and gives the operator one archive rather than a manually assembled folder.
Failure behavior is part of the interface
The script uses strict Bash mode and bounded HTTP timeouts. A failed individual PDF reports its document ID and the batch continues, so one temporary failure does not hide the rest of the work. Empty filter results stop with a named message instead of creating an empty output. Unknown options and missing option values fail before an API request.
What to test before depending on a portal CLI
Test a fresh login, an expired access token with a valid refresh token, a rejected refresh, each date and type filter, explicit IDs, an empty result, an existing PDF, a forced overwrite, a failed individual download, and ZIP export. The expected results are observable: no stored password, protected token files, correct IDs selected, safe re-runs, and clear failure output.
Publication boundary
The implementation is public, but screenshots must not show credentials, bearer or refresh tokens, session cookies, real customer data, account-specific endpoints, full invoice metadata, or local paths that identify an operator. The tool is unofficial and is not affiliated with Hamberger.

