Sign inSign up

romanspies/bankingsync

By romanspies

•Updated 1 day ago

Image
0

10K+

romanspies/bankingsync repository overview

⁠This README might not be representative of the current state of development - please visit GitHub to learn more about the current version of BankingSync!

⁠Your European bank transactions, inside Actual Budget. Automatically.

Docker Hub⁠ · GitHub⁠ · Installation Guide⁠


bankingsync connects to your bank via PSD2 open banking and imports transactions into a self-hosted Actual Budget⁠ instance. It runs on your own machine as a single Docker container — no licence keys, no account limits, no phoning home. Your financial data goes from your bank to your server and nowhere else.

⁠Why bankingsync

  • Broad bank coverage — works with any bank supported by Enable Banking⁠'s PSD2 integration across Europe
  • Connect all your accounts — add as many bank connections as you need, each mapped to its own Actual Budget account (e.g. Revolut → "Revolut", N26 → "N26")
  • No strings attached — fully open source under AGPL-3.0, no licence keys, no paywalls, no usage caps
  • Your data stays yours — transactions travel directly from Enable Banking to your machine, nothing is routed through third-party servers
  • Read-only access — bankingsync uses PSD2 read-only consent, it cannot initiate payments or modify your bank account in any way
  • Pending-to-cleared lifecycle — pending transactions are imported immediately and automatically promoted to cleared once they settle
  • Built-in deduplication — transaction references are persisted so re-syncing the same window never produces duplicates
  • Multi-currency aware — foreign currency transactions are recorded at the settled amount in your account's base currency
  • Rules run automatically — any categorisation or payee rules you have configured in Actual Budget are applied to every new transaction on import
  • Email notifications — get alerted on sync failures and before a bank session needs to be re-authorised, with a test email button to verify your setup
  • TLS out of the box — a self-signed certificate is generated on first start so the web UI is always served over HTTPS
  • Full observability — ship OpenTelemetry metrics and traces to your collector, and continuous profiling data to Grafana Pyroscope
  • Supply chain transparency — every container image ships with a CycloneDX SBOM (Go modules + OS packages) viewable in the web UI, downloadable as JSON, and attached as a BuildKit attestation on Docker Hub
  • Minimal footprint — single Go binary, single Docker container, SQLite for storage, zero runtime dependencies

⁠How it works

Your bank
   |  (read-only OAuth via Enable Banking)
   v
bankingsync (on your machine)
   |
   |--- Fetches transactions since last sync
   |--- Filters out already-imported transaction IDs
   |--- Writes new transactions to Actual Budget
   |--- Promotes pending transactions to cleared when they settle
   |--- Applies your Actual Budget rules to new transactions
   |--- Sends an alert email if anything goes wrong or a session is expiring
   |--- Logs the result to the sync history
   v
Your Actual Budget instance

On first run, bankingsync imports the last 30 days of transactions. After that, it syncs only new data on every cycle. The sync interval is configurable (default: every 6 hours).

⁠Quick start

For a detailed walkthrough, see INSTALLATION.md⁠.

⁠1. Set up Enable Banking

Enable Banking⁠ is the regulated open banking provider that connects bankingsync to your bank.

  1. Sign up at enablebanking.com⁠
  2. Register a new application in the developer portal
  3. Generate your RSA key pair — either let Enable Banking generate it for you during app registration (a .pem file will be saved to your Downloads folder), or create one yourself:
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem

If generating manually, upload public.pem to the developer portal.

  1. Add https://localhost:8443/callback as an allowed redirect URI
⁠2. Find your Actual Budget sync ID

In Actual Budget, go to Settings > Sync > Show file ID. Copy the value.

⁠3. Start bankingsync

Create a docker-compose.yml with the three required variables:

services:
  bankingsync:
    image: romanspies/bankingsync:latest
    container_name: bankingsync
    restart: unless-stopped
    ports:
      - "8443:8443"
    volumes:
      - bankingsync_data:/data
    environment:
      ACTUAL_URL: "http://your-actual-instance:5006"
      ACTUAL_PASSWORD: "your-password"
      ACTUAL_SYNC_ID: "your-sync-id"

volumes:
  bankingsync_data:

See the included docker-compose.yml⁠ for a full example with all optional parameters (email notifications, sync interval, observability, networking, etc.).

docker compose up -d
⁠4. Complete setup in the browser

Open https://localhost:8443⁠ (accept the self-signed cert warning).

If bankingsync is on a remote machine:

ssh -L 8443:[DOCKER_CONTAINER_IP]:8443 yourserver

The web UI walks you through four steps:

  1. Setup — upload your private.pem and enter your Enable Banking Application ID
  2. Connect — pick your country and bank, complete the OAuth flow
  3. Pick Account — choose which bank sub-account to sync (showing IBAN, owner, and currency when available), which Actual Budget account to import into, and from which date to start importing
  4. Status — see your connected accounts, sync history, and watch the first sync run

That's it. bankingsync syncs automatically from here on. Connect additional banks any time from the Connect page — each one maps to a different Actual Budget account.

⁠Configuration

All configuration is via environment variables. Only three are required.

VariableRequiredDefaultDescription
ACTUAL_URLYes—URL of your Actual Budget instance
ACTUAL_PASSWORDYes—Actual Budget server password
ACTUAL_SYNC_IDYes—Budget file sync ID
ACTUAL_ACCOUNTNoRevolutDefault Actual Budget account name (used as the pre-filled value when connecting a new bank; each bank can be mapped to a different account via the web UI)
EB_APPLICATION_IDNo—Enable Banking application ID (locks the field in the UI if set)
SYNC_INTERVAL_HOURSNo6How often to sync
ACCOUNT_HOLDER_NAMENo—Your name(s) as they appear on transactions, comma-separated. Suppresses self-transfers from appearing as payees.
WEB_ADDRNo:8443Web UI listen address
NOTIFY_EMAILNo—Email for sync failure alerts and session expiry warnings
SMTP_HOSTNosmtp.gmail.comSMTP server
SMTP_PORTNo587SMTP port
SMTP_USERNo—SMTP username
SMTP_PASSNo—SMTP password
OTLP_ENDPOINTNo—OTLP gRPC endpoint (e.g. collector:4317) for metrics and traces
PYROSCOPE_SERVER_ADDRESSNo—Grafana Pyroscope URL for continuous profiling
PYROSCOPE_BASIC_AUTH_USERNo—Pyroscope basic auth username
PYROSCOPE_BASIC_AUTH_PASSWORDNo—Pyroscope basic auth password

⁠Data volume

All state lives in a single Docker volume mounted at /data.

PathDescription
/data/bankingsync.dbSQLite database — settings, bank accounts, sync log, sync state, transaction refs
/data/tls.crt, /data/tls.keyTLS certificate and key — auto-generated on first start
/data/private.pemEnable Banking private key — optional alternative to uploading via the web UI

To use your own TLS certificate, place it at /data/tls.crt and /data/tls.key before starting.

⁠Web UI

PagePathDescription
Setup/setupUpload PEM file and set Application ID
Connect/connectBrowse banks by country, start OAuth, add a bank account
Pick Account/pick-accountChoose a sub-account (shows IBAN, owner, currency), set the target Actual Budget account and sync start date
Status/statusView accounts, sync history, trigger sync, test email, reset sync, renew or remove accounts
Test EmailPOST /test-emailSend a test email to verify SMTP configuration
SBOM/sbomBrowse the embedded CycloneDX SBOM — Go module and OS package inventory with licenses. Raw JSON download at /sbom.json.
Health/healthReturns JSON with status (ok/degraded/unhealthy), version, connected accounts, expiring sessions, last sync info. HTTP 503 when unhealthy.

⁠Session renewal

Enable Banking sessions expire after roughly 180 days. bankingsync warns you by email (if configured) when a session is within 7 days of expiry. To renew, click Renew on the Status page and re-authorise with your bank. No data is lost — sync state and transaction history are preserved.

⁠Updating

docker compose pull && docker compose up -d

⁠Building from source

git clone https://github.com/RomanSpies/BankingSync.git
cd bankingsync
go build -o bankingsync .
go test ./...

Requires Go 1.25+. See INSTALLATION.md⁠ for details.

⁠Metrics

When OTLP_ENDPOINT is set, bankingsync exports OpenTelemetry metrics via gRPC:

⁠Counters
MetricDescription
bankingsync_sync_runs_totalSync cycles completed, labelled by status
bankingsync_transactions_added_totalNew transactions imported
bankingsync_transactions_confirmed_totalPending transactions promoted to booked
bankingsync_transactions_skipped_totalTransactions skipped (already imported)
bankingsync_rules_applied_totalRule actions applied to new transactions
bankingsync_commit_errors_totalErrors committing to Actual Budget
⁠Histograms
MetricDescription
bankingsync_sync_duration_secondsWall-clock duration of a full sync cycle
bankingsync_fetch_duration_secondsDuration of the Enable Banking fetch
⁠Gauges
MetricDescription
bankingsync_pending_transactionsPending transactions awaiting confirmation
bankingsync_session_expiry_daysDays until session expires

⁠Migrating from a previous version

If upgrading from a version that used state.json:

  1. Keep your /data volume in place
  2. Pull and restart: docker compose pull && docker compose up -d
  3. state.json is automatically migrated into bankingsync.db and renamed to state.json.migrated
  4. If /data/private.pem exists, it is detected automatically
  5. Re-authorise your bank from the Connect page (sync state and transaction refs are preserved — no duplicates)

⁠License

GNU Affero General Public License v3.0 — see LICENSE⁠.

See THIRD_PARTY_NOTICES.md⁠ for dependency licenses.

Tag summary

Content type

Image

Digest

sha256:b921128f2…

Size

12.2 MB

Last updated

1 day ago

docker pull romanspies/bankingsync