Sign inSign up

darktrees/ynab-importer

By darktrees

•Updated 2 days ago

Self-hosted OFX importer for YNAB that matches and categorises with a local model

Image
0

1.2K

darktrees/ynab-importer repository overview

⁠ynab-importer

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.

⁠What it does

  • Categories learned from your history. One sync trains a per-user model on your past YNAB transactions: a rule per merchant where you've been consistent, and a text classifier for the rest. Every row shows how sure it is, with its top three guesses at the head of the category list.
  • Recognises what's already there. A line you entered by hand, with a typo'd amount, a bill that went up, or a scheduled transaction YNAB dated on a different day, is corrected and cleared in place instead of imported a second time. Whatever is in YNAB for that period but not in the file is listed, so a missed entry is visible.
  • Payees reused. Lines are linked to the payees you already have, and a new one is flagged before it's created.
  • Transfers and schedules. A transfer between your own accounts is imported as a YNAB transfer, not an expense. A line that matches a scheduled transaction takes its payee, category and split, and any row can be tagged recurring to create a new schedule.
  • New categories on the spot. Create a category, and its group, from the review screen without leaving it.
  • It keeps learning. Every category you confirm on import goes back into the model.
  • Several people, one install. Each person signs in with an emailed link, connects their own budget, and gets their own history and model.

⁠Privacy

  • Your transactions go to YNAB and nowhere else. Categorising, matching and training all happen inside the container. There is no AI service, no analytics and no telemetry.
  • The bank file isn't kept. It is parsed into the review screen and then discarded. Only the lines you import are added to your history, so the model can learn from them.
  • What is stored, and how. Each person's history (bank description, payee, category, amount and date) and their model sit in their own folder under /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.
  • Delete everything in one step. Settings → Delete all my data erases your history, your model, your stored token and every session. Nobody else's data is touched, and the next sync rebuilds the history from YNAB.

⁠Running it

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.

⁠Configuration

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.

VariableRequiredWhat it's for
SECRET_KEYyesSigns Flask's cookie. Without a fixed value, each worker makes its own and sessions break between them
ENCRYPTION_KEYyesAES-256-GCM key for email addresses and YNAB tokens at rest. Changing it strands what's stored
EMAIL_HASH_KEYyesPepper for looking people up by email. Changing it means nobody can sign in again
ALLOWLISTyesComma-separated addresses that may sign in. Empty lets nobody in
SITE_URLyesThe public address, used to build sign-in links, e.g. https://ynab.example.com
SMTP2GO_API_KEYyesSends the sign-in emails
EMAIL_FROMyesThe sender of those emails, at a domain your SMTP2GO account has verified
UPDATE_CHECKnooff stops the check for a newer image on Docker Hub
UPDATE_CHECK_IMAGEnoThe 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.

⁠Data and attribution

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.

SourceUsed forTerms
YNAB API⁠Reading your budget's history, accounts, payees and schedules; writing the importYour 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 exportThe transactions to importYour 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 emailsThe operator's own account and SMTP2GO's terms. Nothing ships
Docker Hub⁠Checking whether a newer image existsA 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.

⁠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.

⁠Source

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.

Tag summary

Content type

Image

Digest

sha256:b9a0a7b36…

Size

133.4 MB

Last updated

2 days ago

docker pull darktrees/ynab-importer