Self-hosted OFX importer for YNAB that matches and categorises with a local model
1.2K
A self-hosted importer that takes the OFX or QFX file your bank exports and brings it into YNAB, with every line already categorised and reconciled against what's in your budget.
YNAB's own file import gives you a list of new transactions to categorise one by one, and it duplicates anything you'd already typed in by hand unless the amount matches to the cent. This app learns your categories from your own YNAB history and works out which lines are already in the budget, so a review is mostly confirming what it guessed. Categorisation runs on a small local model; no transaction is sent to any AI or third-party service.
It is not affiliated with or endorsed by YNAB.
/app/data. Email addresses and YNAB tokens are encrypted with
AES-256-GCM. Sessions and sign-in links are stored only as hashes. The
history itself isn't encrypted, so protect the volume as you would the
budget.services:
ynab-importer:
image: darktrees/ynab-importer:latest
env_file: .env
volumes:
- ./data:/app/data
ports:
- "5000:5000"
restart: unless-stopped
The container runs as uid 1000 and keeps everything in /app/data: a shared
sign-in database, and each person's transaction history and model. Create the
folder and hand it over before the first start:
mkdir -p data && sudo chown 1000:1000 data
Then sign in at /login, connect a budget by pasting a
YNAB personal access token, and
press Sync history from YNAB once to train. Back up ./data with the
keys below: a token stored there can only be read with ENCRYPTION_KEY.
Exposing it. There are no passwords. Sign-in is a single-use link emailed
only to the addresses in ALLOWLIST, the session is a server-side record
behind an httpOnly cookie, sign-in is rate-limited, and every form checks its
origin. Put it behind a reverse proxy with a trusted certificate and set
SITE_URL to that https address; the cookie is marked Secure when it is. It
holds a token that can change your budget, so a private network is still the
safer place for it.
Put these in .env beside the compose file. Never put them in the compose
file itself. Generate each key separately with openssl rand -hex 32.
| Variable | Required | What it's for |
|---|---|---|
SECRET_KEY | yes | Signs Flask's cookie. Without a fixed value, each worker makes its own and sessions break between them |
ENCRYPTION_KEY | yes | AES-256-GCM key for email addresses and YNAB tokens at rest. Changing it strands what's stored |
EMAIL_HASH_KEY | yes | Pepper for looking people up by email. Changing it means nobody can sign in again |
ALLOWLIST | yes | Comma-separated addresses that may sign in. Empty lets nobody in |
SITE_URL | yes | The public address, used to build sign-in links, e.g. https://ynab.example.com |
SMTP2GO_API_KEY | yes | Sends the sign-in emails |
EMAIL_FROM | yes | The sender of those emails, at a domain your SMTP2GO account has verified |
UPDATE_CHECK | no | off stops the check for a newer image on Docker Hub |
UPDATE_CHECK_IMAGE | no | The image that check looks at. Defaults to darktrees/ynab-importer |
YNAB tokens are not configuration: each person connects their own from inside the app, and it is stored encrypted.
The home screen shows the build the container is running, and a banner when a
newer image has been published. Update with docker compose pull && docker compose up -d.
This project's licence covers its own code. Whoever runs the image brings their own accounts and agrees to each provider's terms themselves. No financial data ships in the image, and none leaves your server except to YNAB.
| Source | Used for | Terms |
|---|---|---|
| YNAB API | Reading your budget's history, accounts, payees and schedules; writing the import | Your own YNAB account and the YNAB API Terms of Service. Called at runtime with each person's own token; nothing ships |
| Your bank's OFX/QFX export | The transactions to import | Your own data. The file itself is not stored; the lines you import join your history in ./data to train on, and go nowhere but YNAB |
| SMTP2GO | Sending sign-in emails | The operator's own account and SMTP2GO's terms. Nothing ships |
| Docker Hub | Checking whether a newer image exists | A public tag lookup, nothing about you is sent. Off with UPDATE_CHECK=off |
The image includes Flask, scikit-learn, joblib and Requests (BSD-3-Clause / Apache-2.0), ofxparse (MIT), cryptography (Apache-2.0 / BSD) and gunicorn (MIT), each under its own licence.
ynab-importer is source-available under the
PolyForm Noncommercial License 1.0.0.
Personal, hobby, research, educational and charity use are all welcome.
Commercial use is not permitted without a separate licence. The full terms
are at /app/LICENSE in the image.
The code lives on a private forge. It's available on request for non-commercial use; get in touch through the contact link on the darktrees Docker Hub profile. To ask about commercial use, use the same link.
Content type
Image
Digest
sha256:b9a0a7b36…
Size
133.4 MB
Last updated
2 days ago
docker pull darktrees/ynab-importer