An automation tool that connects to Kivra (a Swedish digital mailbox service) to download and organize your digital receipts and letters, helping you maintain a local backup of your important documents.
This tool is originaly based on a script created here https://github.com/stefangorling/fetch-kivra, thanks Stefan! It has since gone through major changes and feature additions.
This project is not affiliated with Kivra in any way. It is an independent, open-source tool created by the community to help users download their documents from Kivra - a feature that is otherwise only available through manual clicks in their user interface.
We believe individuals should have easy access to their own data. Digital mailboxes like Kivra hold important documents on behalf of users, and the lack of an efficient export option makes it unnecessarily cumbersome for users to retrieve and back up their documents. This tool aims to bridge that gap.
Use it responsibly and at your own discretion.
- Authentication: Fetches and displays a QR code for authentication via BankID.
- Flexible Storage: Store documents locally on your filesystem or integrate with Paperless-ngx for advanced document management
- Multiple Interaction Modes: Scan the BankID QR code right in your terminal, or set up a "headless" mode that listens for triggers and shows QR codes via a web page or ntfy.sh
This will:
- Authenticate with Kivra using BankID
- Fetch receipts and letters
- Store them in the filesystem
Build the image, then run it interactively. The BankID QR code is drawn directly in your terminal and refreshes until you scan it with the BankID app:
docker build -t kivra-sync .
docker run -it --rm -v "$(pwd)/data:/data" kivra-sync:latest YYYYMMDDXXXX --base-dir /dataDocuments are saved to ./data on your machine.
-itis required, since the QR code is shown in the terminal.--base-dir /datamakes the documents land in the mounted volume rather than inside the container.- Before login, the terminal must be at least 60 columns × 32 rows. A typical compact QR and its status line need about 54 columns × 29 rows; larger payloads may require more space, which the error message will report.
For a long-running setup without an interactive terminal, use the web interaction provider and open the QR code in your browser:
# Edit docker-compose.yml to set your configuration,
# especially "your-national-identification-number-here"
# Then run:
docker compose build && docker compose upThen navigate to http://localhost:8080 to trigger a sync and scan the QR code.
The supplied Compose setup has neither an interactive terminal for terminal nor a desktop image viewer for local, so it uses web. The ntfy provider is another option for headless runs.
# Install system dependencies (Debian/Ubuntu)
apt-get install weasyprint
# Install Python dependencies
pip install -r requirements.txt
# Run the script
python kivra_sync.py YYYYMMDDXXXXIf you have Nix with flakes enabled:
# Run (uses the flake-provided environment)
nix run . -- --help
# Typical run, storing under ./data/<ssn>/
nix run . -- YYYYMMDDXXXX --base-dir ./data
# Run with the web interaction provider
nix run . -- YYYYMMDDXXXX --interaction-provider web --web-port 8080
# Build and run help on resulting executable
nix build && result/bin/kivra-sync --help
# Dev shell with Python + deps + system libs
nix develop
Documents are stored in the local filesystem.
python kivra_sync.py YYYYMMDDXXXX --storage-provider filesystem --base-dir /path/to/storeUpload documents directly to Paperless-ngx:
python kivra_sync.py YYYYMMDDXXXX --storage-provider paperless \
--paperless-url http://your-paperless-server:8000/api \
--paperless-token your_paperless_api_token \
--paperless-tags "kivra,receipts,automated"Interaction providers handle user interaction during authentication and report completion statistics.
Draws the BankID QR code directly in the terminal and refreshes it on every poll, so the code you scan is always current. Reports progress to the console:
python kivra_sync.py YYYYMMDDXXXX --interaction-provider terminalUses Unicode half-block characters to fit two QR rows into each terminal line. Requires an interactive terminal (use docker run -it in Docker) with a font that supports these characters. Startup checks for at least 60 columns × 32 rows before authentication; the exact size is checked again when the QR payload arrives. A typical compact QR and its status line need about 54 columns × 29 rows, but larger payloads may require more. Windows console ANSI handling is initialized automatically. If output isn't a terminal, use the web or ntfy provider instead.
When upgrading, cron, systemd, and Docker runs without -it must select web or ntfy explicitly because terminal is now the default. If the window is resized too small during authentication, QR updates pause until it is enlarged again.
Provides a web interface for triggering syncs and viewing results:
python kivra_sync.py YYYYMMDDXXXX --interaction-provider web --web-port 8080Then navigate to http://localhost:8080 in your browser to access the interface. Perfect for containerized deployments where GUI access is not available.
Sends QR codes and reports via ntfy.sh, with optional listening mode:
python kivra_sync.py YYYYMMDDXXXX --interaction-provider ntfy --ntfy-topic your-topicTo trigger the script in listening mode, send the trigger message to the ntfy topic:
curl -d "run now" ntfy.sh/your-topicOpens the QR code image in your system's image viewer. It only shows a single static frame, which BankID may reject as stale ("QR-koden är ogiltig" / "QR code is invalid", RFA17). This provider will be removed in a future release; use terminal instead.
python kivra_sync.py YYYYMMDDXXXX --interaction-provider localCustomize which documents to fetch and how many:
# Fetch only letters
python kivra_sync.py YYYYMMDDXXXX --no-fetch-receipts
# Fetch only receipts
python kivra_sync.py YYYYMMDDXXXX --no-fetch-letters
# Fetch up to 10 letters and 5 receipts
python kivra_sync.py YYYYMMDDXXXX --max-letters 10 --max-receipts 5
# Fetch unlimited receipts
python kivra_sync.py YYYYMMDDXXXX --max-receipts 0| Option | Description |
|---|---|
YYYYMMDDXXXX (positional) |
Personal identity number |
--dry-run |
Do not actually store documents, just simulate |
| Option | Description |
|---|---|
--storage-provider {filesystem,paperless} |
Storage provider to use (default: filesystem) |
--base-dir DIR |
Base directory for storing documents (default: current working directory) |
| Option | Description |
|---|---|
--paperless-url URL |
Paperless API URL (required for paperless) |
--paperless-token TOKEN |
Paperless API token (required for paperless) |
--paperless-tags TAGS |
Comma-separated list of tags to apply to all documents |
| Option | Description |
|---|---|
--interaction-provider {terminal,web,ntfy,local} |
Interaction provider to use (default: terminal; local is deprecated) |
--ntfy-topic TOPIC |
ntfy topic to send notifications to (required for ntfy) |
--ntfy-server URL |
ntfy server URL (default: https://ntfy.sh) |
--ntfy-user USER |
ntfy username for authentication |
--ntfy-pass PASS |
ntfy password for authentication |
--web-port PORT |
Port for web interface (default: 8080) |
--web-host HOST |
Host for web interface (default: 0.0.0.0) |
--trigger-message MSG |
Message that triggers the script (default: "run now") |
| Option | Description |
|---|---|
--fetch-receipts / --no-fetch-receipts |
Enable/disable receipt fetching (default: enabled) |
--fetch-letters / --no-fetch-letters |
Enable/disable letter fetching (default: enabled) |
--max-receipts N |
Maximum number of receipts to fetch (0 for unlimited) |
--max-letters N |
Maximum number of letters to fetch (0 for unlimited) |
The code is organized into logical modules:
kivra/: Kivra-specific functionality (auth, API, models)storage/: Document storage providersinteraction/: User interaction providersutils/: Utility functions