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.
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.
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.
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
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
$ 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.
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.
.pfx file and choose a PIN. Both go into your tax directory.[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.
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.
der jahresrhythmus
What a typical freelancer or sole trader files, and when:
| cadence | form | command |
|---|---|---|
| monthly or quarterly | UStVA (VAT report) | viking ustva --period 41 |
| yearly | EÜR (profit/loss statement) | viking euer |
| yearly | USt (annual VAT return) | viking ust |
| yearly | GewSt (trade tax return) | viking gewst |
| yearly | ESt (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:
~/Taxes/2026, and run viking init in it. You get a commented viking.conf to fill in.viking.conf plus one invoice TSV per income source, per the formats under project files.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.--dry-run, one at a time. Later, viking list and viking download collect the Bescheide when the Finanzamt answers.befehle
| command | files | notes |
|---|---|---|
viking ustva | UStVA | VAT advance report. --period required: 41-44 quarters, 01-12 months, or words like q1, mar, 03. |
viking euer | EÜR | Annual profit/loss per source. One EÜR per income source. |
viking ust | USt | Annual VAT return per source. Picks up vorauszahlungen=. |
viking gewst | GewSt 1 A | Trade tax return, typ=gewerbe sources only. Gewinn and Hinzurechnungen come from the source's TSV. |
viking est | ESt + Anlagen | Income tax return. Aggregates every source; no -s. |
viking iban | Change the bank account the Finanzamt knows. --new-iban required. | |
viking message | Free-text letter to the Finanzamt. --subject + --text or --text-file. | |
viking list / download | ELSTER Postfach: list documents, download Bescheide. -o picks the directory, -f overwrites. | |
viking fetch | Install or verify the ERiC runtime. --check reports the version. | |
viking init | Seed a commented viking.conf and TSV skeletons in a directory. |
| flag | effect |
|---|---|
-s, --source | Pick the income source by handle. Optional when exactly one matches. |
-c, --conf | Explicit conf path; replaces the search chain. |
--dry-run | Validate via ERiC and print the XML. Nothing is sent. |
--test | Submit to the ELSTER sandbox with a Testmerker. Needs a test certificate. |
-v, --verbose | Print the generated XML and full server responses. |
-o, --output-pdf | Render the official paper form to a PDF via ERiC. |
-D, --data-dir | Override 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.
ein ordner, ein jahr
One directory per tax year, kept in git if you like. viking reads exactly these files and nothing else:
| file | what it is |
|---|---|
viking.conf | INI: taxpayers, kids, income sources, signing. The only required file; year= inside it fixes the tax year. |
<source>.tsv | The invoice ledger an euer= source points at. Income and expenses, one row per booking. |
rente.tsv | Pension rows for a typ=rente source. One row per payer. |
abzuege.tsv | ESt deductions (Vorsorge, Sonderausgaben, Belastungen, per-kid amounts), referenced by the taxpayer. |
viking.pfx + viking.pin | Your 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].
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
amount is required; everything else is optional.19, 7, 0 (steuerfrei, reverse charge), -1 (nicht steuerbar, e.g. refunded prior-year VAT). A trailing % is tolerated.category column tags an expense as a GewSt Hinzurechnung (dauerschuldzinsen, renten-dauernde-lasten, stille-gewinnanteile, miete-beweglich, miete-elektro, miete-unbeweglich). Untagged rows stay ordinary expenses.viking ustva --period; a source with no TSV at all files a Nullmeldung with a warning.The annotated example/ directory in the repo shows every file and every key in use.