viking

Documentation.

Install it, work through the year, file four forms. This page is the operator's manual; the README in the repo carries the same information for offline use.

ᚠInstall

installation

viking is a single static binary. Prebuilt ones live on the releases page: viking-linux, viking-macos (universal) and viking.exe. Afterwards viking fetch pulls the ERiC runtime.

linux

mkdir -p ~/.local/bin
curl -fsSL -o ~/.local/bin/viking \
  https://github.com/capocasa/viking/releases/latest/download/viking-linux
chmod +x ~/.local/bin/viking

If ~/.local/bin is not on your PATH yet: put the export in ~/.profile for bash (login shells, so the PATH reaches everything you launch) or ~/.zshrc for zsh, which never reads .profile:

# bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.profile
# zsh
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc

Debian and Ubuntu ship a stock .profile that already adds ~/.local/bin once it exists; re-login and you are done.

macos

mkdir -p ~/.local/bin
curl -fsSL -o ~/.local/bin/viking \
  https://github.com/capocasa/viking/releases/latest/download/viking-macos
chmod +x ~/.local/bin/viking
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc

windows

One paste into PowerShell: installs to ~\bin, puts it on your user PATH, prints the version.

md "$env:USERPROFILE\bin" -Force | Out-Null
curl.exe -fsSL -o "$env:USERPROFILE\bin\viking.exe" https://github.com/capocasa/viking/releases/latest/download/viking.exe
$p = [Environment]::GetEnvironmentVariable('Path','User')
if (-not ($p -split ';' -contains "$env:USERPROFILE\bin")) {
  [Environment]::SetEnvironmentVariable('Path', "$p;$env:USERPROFILE\bin", 'User')
}
& "$env:USERPROFILE\bin\viking.exe" --version

Open a new terminal afterwards; running ones keep their old PATH. ERiC also needs the Microsoft VC++ Redistributable, which stock Windows ships without:

winget install Microsoft.VCRedist.2015+.x64

the ERiC runtime

$ viking fetch   # downloads and extracts ERiC (~340 MB) into your data dir

ERiC is the official ELSTER rich client library, published by the Bavarian tax office. viking downloads it from download.elster.de and keeps it under your data directory. viking fetch --check reports the installed version and the form years it covers.

ᚦELSTER setup

anschluss ans finanzamt

ELSTER is the German tax portal, and it has two faces: the website where you register, and the machine interface viking speaks over the official ERiC library. You need both exactly once.

  1. Create an ELSTER account at elster.de (Mein ELSTER). Store the User-ID file and the password in your tax directory; you will need the portal for things viking does not do yet.
  2. Order a signing certificate. In Mein ELSTER, create a Zertifikat. You receive a .pfx file and choose a PIN. Both go into your tax directory.
  3. Wire it into the conf so live submissions can sign:
[auth]
cert = viking.pfx            # the .pfx you got from ELSTER
pin  = viking.pin            # file containing the PIN (or the PIN inline)
# pincmd = pass show elster/pin   # or any command that prints the PIN

pin= takes a file path or the PIN itself; pincmd= runs any shell command (pass, 1Password CLI, macOS Keychain, libsecret) with the conf directory as cwd. Exactly one of the two. --dry-run works without any of this.

trying it out: the sandbox

ELSTER publishes test certificates for a public sandbox. The pipeline runs for real, including signing and server answers, but nothing touches your Finanzamt:

curl -LO https://download.elster.de/download/schnittstellen/Test_Zertifikate.zip
unzip Test_Zertifikate.zip          # PIN for all certs: 123456

Point [auth] at test-soft-pse.pfx (personal) or test-softorg-pse.pfx (business), write 123456 into the pin file, and add --test to any command.

ᚢWorkflow

der jahresrhythmus

What a typical freelancer or sole trader files, and when:

cadenceformcommand
monthly or quarterlyUStVA (VAT report)viking ustva --period 41
yearlyEÜR (profit/loss statement)viking euer
yearlyUSt (annual VAT return)viking ust
yearlyGewSt (trade tax return)viking gewst
yearlyESt (personal income tax return)viking est

Which of these apply to you is worth a sanity check before you file. A pure Freiberufler (consultant, artist, most developers) owes no Gewerbesteuer and skips GewSt. Past roughly net €80k profit a year, §141 AO can obligate double-entry bookkeeping with a balance sheet, which is outside viking's EÜR model; a plain-text ledger like ledger is the tool for that lane.

The yearly routine, start to finish:

  1. Pick an AI you trust with bank statements. A local model, or a provider where you have a no-training guarantee. The choice is yours; the data is not hypothetical.
  2. Make a directory for the year, e.g. ~/Taxes/2026, and run viking init in it. You get a commented viking.conf to fill in.
  3. Set up ELSTER once. Create an account at elster.de, order your signing certificate, and store the login material plus the certificate files in the same directory. See ELSTER setup.
  4. Feed the year in. Give the AI access to your mailbox if you can, export a full-year bank statement, and have it extract every client invoice and anything plausibly a business expense or a deduction.
  5. Let it write the files. Point the AI at github.com/capocasa/viking and tell it to produce viking.conf plus one invoice TSV per income source, per the formats under project files.
  6. Dry-run everything. Have it run viking euer, ust, gewst and est with --dry-run, read the XML output, and render the official PDFs with -o form.pdf. Check them for plausibility yourself, or feed them back to the AI.
  7. File. Same commands without --dry-run, one at a time. Later, viking list and viking download collect the Bescheide when the Finanzamt answers.
  8. Keep the receipts. The TSVs are the audit trail; the Belege they summarize belong in the same directory. The Finanzamt can ask years later.
Wichtiger HinweisSubmissions are irreversible. Dry-run first, sandbox second, production last. This is experimental software, verify independently.

ᚱCommands & flags

befehle

commandfilesnotes
viking ustvaUStVAVAT advance report. --period required: 41-44 quarters, 01-12 months, or words like q1, mar, 03.
viking euerEÜRAnnual profit/loss per source. One EÜR per income source.
viking ustUStAnnual VAT return per source. Picks up vorauszahlungen=.
viking gewstGewSt 1 ATrade tax return, typ=gewerbe sources only. Gewinn and Hinzurechnungen come from the source's TSV.
viking estESt + AnlagenIncome tax return. Aggregates every source; no -s.
viking ibanChange the bank account the Finanzamt knows. --new-iban required.
viking messageFree-text letter to the Finanzamt. --subject + --text or --text-file.
viking list / downloadELSTER Postfach: list documents, download Bescheide. -o picks the directory, -f overwrites.
viking fetchInstall or verify the ERiC runtime. --check reports the version.
viking initSeed a commented viking.conf and TSV skeletons in a directory.

shared flags

flageffect
-s, --sourcePick the income source by handle. Optional when exactly one matches.
-c, --confExplicit conf path; replaces the search chain.
--dry-runValidate via ERiC and print the XML. Nothing is sent.
--testSubmit to the ELSTER sandbox with a Testmerker. Needs a test certificate.
-v, --verbosePrint the generated XML and full server responses.
-o, --output-pdfRender the official paper form to a PDF via ERiC.
-D, --data-dirOverride where the ERiC runtime and logs live.

Exit codes: 0 fine, 2 bad CLI usage, 3 configuration problem, 4 referenced file missing, 5 ELSTER rejected something. Success is silent, like a Unix tool; the server's answers land in the per-year log under your data dir.

ᚨProject files

ein ordner, ein jahr

One directory per tax year, kept in git if you like. viking reads exactly these files and nothing else:

filewhat it is
viking.confINI: taxpayers, kids, income sources, signing. The only required file; year= inside it fixes the tax year.
<source>.tsvThe invoice ledger an euer= source points at. Income and expenses, one row per booking.
rente.tsvPension rows for a typ=rente source. One row per payer.
abzuege.tsvESt deductions (Vorsorge, Sonderausgaben, Belastungen, per-kid amounts), referenced by the taxpayer.
viking.pfx + viking.pinYour ELSTER certificate and its PIN, wired via [auth].

The conf is loaded from ~/.config/viking/viking.conf (global defaults), then ./viking.conf, or from exactly one file when given --conf. Section names: [steuerzahler] (you, plus optionally a differently-named second one as co-filing spouse), [kind] per child, [einkommen] per income source (typ=freiberuflich, gewerbe, kapital, rente), [auth].

the invoice ledger

amount	rate	date	id	description	category
1200	19	2025-01-15	INV-001	Consulting January
500	7	2025-05-12	INV-004	Workshop materials
-300	19	2025-02-01	EXP-001	Office supplies
-96	0	2025-12-31	LOAN-1	Loan interest	dauerschuldzinsen
-4800	19	2025-01-02	RENT-1	Office rent	miete-unbeweglich

The annotated example/ directory in the repo shows every file and every key in use.