A self-hosted web application that syncs bank transactions from Comdirect to YNAB with automatic categorization and manual review.
Support the development of this free, open-source project!
Buy me a coffee on Ko-fi — Your support helps keep this project alive and growing!
Read the Development Blog — Follow along with the development journey and technical insights.
BudgetBuddy bridges the gap between your German bank account (Comdirect) and YNAB (You Need A Budget). Instead of manually entering transactions or relying on unreliable import files, BudgetBuddy:
- Connects directly to Comdirect using their official API with secure TAN verification
- Automatically categorizes transactions using your custom rules (regex, contains, or exact match)
- Flags special transactions like Amazon or PayPal that need manual review
- Lets you review everything before importing to YNAB
- Imports with one click to your YNAB budget
- Comdirect OAuth flow with Push-TAN support
- YNAB Personal Access Token authentication
- Transaction review with manual categorization
- Batch import to YNAB
- Rule-based categorization engine
- Pattern matching (regex, contains, exact)
- Priority ordering
- Import/export rules as JSON
- Amazon transaction detection with order history links
- PayPal transaction detection with activity links
- Configurable external link templates
- Duplicate detection
- Transaction history
- Split transactions (multiple categories)
| Component | Technology |
|---|---|
| Frontend | F# Elmish.React + Feliz + TailwindCSS + DaisyUI |
| Backend | F# Giraffe + Fable.Remoting |
| Database | SQLite |
| Deployment | Docker + Tailscale |
- .NET 9+ SDK
- Node.js 20+
- Docker (for deployment)
# Clone and install
git clone https://github.com/heimeshoff/BudgetBuddy.git
cd BudgetBuddy
npm install
# Start backend (Terminal 1)
cd src/Server && dotnet watch run
# Start frontend (Terminal 2)
npm run devOpen http://localhost:5173 for the frontend (proxies API calls to backend on port 5001).
BudgetBuddy has comprehensive test coverage including unit tests, property-based tests, and integration tests.
Unit Tests (default):
dotnet test
# Runs all unit tests (82 tests)
# ✅ No external API calls
# ✅ Fast (~1 second)Integration Tests (with real APIs):
Integration tests make real API calls to YNAB and Comdirect (including Push-TAN). They are disabled by default and must be explicitly enabled.
# Option 1: Set in .env file
echo "RUN_INTEGRATION_TESTS=true" >> .env
dotnet test
# Option 2: Set as environment variable
RUN_INTEGRATION_TESTS=true dotnet test- Make real YNAB API requests (consuming your rate limit)
- Initiate Comdirect OAuth flow with Push-TAN sent to your phone
- Require valid credentials in
.envfile
For manual testing of integrations without the full UI:
# Test YNAB API integration
dotnet fsi scripts/test-ynab.fsx
# - Fetches budgets, accounts, categories
# - Validates token
# - Shows detailed output
# Test Comdirect OAuth flow
dotnet fsi scripts/test-comdirect.fsx
# - Initiates OAuth with Push-TAN
# - Waits for phone confirmation
# - Fetches transactions (if account ID provided)Setup for scripts:
- Copy
.env.exampleto.env - Fill in your API credentials:
YNAB_TOKEN=your-ynab-token COMDIRECT_CLIENT_ID=your-client-id COMDIRECT_CLIENT_SECRET=your-client-secret COMDIRECT_USERNAME=your-username COMDIRECT_PASSWORD=your-password # COMDIRECT_ACCOUNT_ID=your-account-id # Optional - Run the scripts
See scripts/README.md for detailed documentation on the test scripts.
If you have categorization rules from a legacy system in YAML format, you can import them:
# List available YNAB budgets
dotnet fsi scripts/import-rules.fsx --list
# Import rules for a specific budget
dotnet fsi scripts/import-rules.fsx "My Budget"
# Clear all rules and reimport (useful after DB reset)
dotnet fsi scripts/import-rules.fsx --clear "My Budget"Expected YAML format (rules.yml in project root):
rules:
- match: "REWE"
category: "Groceries"
- match: "Netflix"
category: "Entertainment"The script:
- Fetches categories from YNAB and matches by name
- Creates rules with
Containspattern type (substring match) - Skips duplicates (unless
--clearis used) - Reports which categories couldn't be matched
| Test Type | Count | Description |
|---|---|---|
| Unit Tests | 82 | Fast, no I/O, always run |
| Integration Tests | 6 | Real API calls, opt-in only |
| Property-Based | 3 | FsCheck tests for edge cases |
| Total | 88 | Full test suite |
You'll need:
- Comdirect API credentials (Client ID, Client Secret from Comdirect developer portal)
- YNAB Personal Access Token (from YNAB account settings)
These are entered in the Settings page and stored encrypted locally.
# Build image
docker build -t budgetbuddy .
# Run locally
docker run -p 5001:5001 -v $(pwd)/data:/app/data budgetbuddyFor secure access from anywhere on your Tailnet:
# 1. Generate an encryption key for settings (REQUIRED)
openssl rand -base64 32
# 2. Create .env file with required secrets
cat > .env << EOF
TS_AUTHKEY=tskey-auth-xxx
BUDGETBUDDY_ENCRYPTION_KEY=<paste-generated-key-here>
EOF
# 3. Deploy
docker-compose up -dYour app is now accessible at https://budgetbuddy.<your-tailnet>.ts.net.
⚠️ Important: TheBUDGETBUDDY_ENCRYPTION_KEYis required for settings to persist across container rebuilds. Without it, encrypted settings (YNAB token, Comdirect credentials) will be lost on eachdocker-compose up --build. Back up this key securely!
src/
├── Shared/ # Domain types + API contracts
│ ├── Domain.fs # Transaction, Rule, SyncSession types
│ └── Api.fs # Fable.Remoting API interfaces
├── Client/ # Elmish frontend
│ ├── State.fs # Model, Msg, update (MVU)
│ ├── View.fs # UI components (Feliz)
│ └── Views/ # Page-specific views
├── Server/ # Giraffe backend
│ ├── ComdirectClient.fs # Comdirect API integration
│ ├── YnabClient.fs # YNAB API integration
│ ├── RulesEngine.fs # Categorization logic
│ ├── Persistence.fs # SQLite operations
│ └── Api.fs # API implementation
└── Tests/ # Expecto tests
├── *Tests.fs # Unit tests
└── *IntegrationTests.fs # Integration tests (opt-in)
scripts/
├── test-ynab.fsx # Interactive YNAB API tester
├── test-comdirect.fsx # Interactive Comdirect OAuth tester
├── import-rules.fsx # Import rules from legacy YAML file
├── EnvLoader.fsx # .env file loader
└── README.md # Test scripts documentation
docs/
├── MILESTONE-PLAN.md # Detailed implementation plan
├── banksync-produktspezifikation.md # Product requirements
└── *.md # Architecture guides
legacy/ # Original CLI implementation (reference)
1. User clicks "Start Sync"
2. App initiates Comdirect OAuth
3. User receives Push-TAN on phone and confirms
4. App fetches transactions from Comdirect
5. Rules engine auto-categorizes where possible
6. User reviews transactions:
- Green: Auto-categorized (ready)
- Yellow: Needs attention (Amazon, PayPal)
- Red: Uncategorized (manual input needed)
7. User confirms and clicks "Import"
8. Transactions sent to YNAB
- No permanent storage of bank credentials - TAN required for each sync
- Sensitive settings encrypted at rest using AES-256 with your
BUDGETBUDDY_ENCRYPTION_KEY - No cloud dependency - runs entirely on your hardware
- Tailscale networking - optional private access without public exposure
| Guide | Purpose |
|---|---|
| Milestone Plan | Step-by-step implementation guide |
| Architecture | System overview |
| Frontend Guide | Elmish + Feliz patterns |
| Backend Guide | Giraffe + Fable.Remoting |
| Persistence | SQLite patterns |
| Deployment | Docker setup |
| Tailscale | Private network |
See MILESTONE-PLAN.md for the complete implementation roadmap with 16 milestones covering all planned features.
Contributions welcome! Please read the architecture docs first to understand the patterns used in this codebase.
This project is released into the public domain under the Unlicense. See LICENSE for details.
Note: This project is not affiliated with Comdirect or YNAB. Use at your own risk. Always verify transactions before importing.