A sidecar that feeds a self-hosted Sure instance with bank transactions from Enable Banking (PSD2 access to Nordea SE).
Generic bank feeds land in Sure with a useless generic name while the actual merchant hides in the transaction details. This sidecar fetches the raw bank data itself and extracts a proper human-readable name (KORTKÖP 260718 ICA SUPERMARKET ARSTA → ICA SUPERMARKET ARSTA) before ingesting into Sure, preserving the full original details in the transaction notes.
(4x/day max, PSD2) (idempotent)
Nordea SE ──► Enable Banking API ──► fetch ──► raw JSON dumps ──► ingest ──► Sure API
$STATE_DIR/raw name extraction
fetch pulls transactions (and balances) for every authorized account and writes them unmodified to disk — the only step that talks to the bank (PSD2 background access allows ~4 fetches/day). ingest maps each dump (name extraction, merchant, expense/income) and POSTs it to Sure; external_id (derived from the immutable entry_reference + account identification_hash) makes re-ingestion idempotent.
-
Register an application in the Enable Banking Control Panel (environment
PRODUCTION, orSANDBOXto try it out), whitelist your redirect URL, and save the private key assecrets/enablebanking.pem. -
In Sure, create a read_write API key (Settings → API Keys).
-
cp .env.example .envand fill inEB_APP_ID,EB_REDIRECT_URLandSURE_API_KEY. -
Authorize with the bank (interactive BankID, repeat every ~180 days):
sure-eb-ingest auth sure-eb-ingest fetch -strategy longest # right after auth: grab the full history windowIf the redirect can't reach the machine, copy the URL you landed on and run
sure-eb-ingest auth -code '<full redirect URL>'. -
Map bank accounts to Sure accounts:
sure-eb-ingest mapprints both sides plus a readySURE_ACCOUNT_MAPtemplate for.env. -
Run:
sure-eb-ingest run # sidecar loop: fetch + ingest every FETCH_INTERVAL (default 6h)
sure-eb-ingest help lists all commands (auth, accounts, fetch, preview, ingest, run, aspsps, sure-accounts, map, app, version). Every setting is documented in .env.example and config.example.yaml; the environment wins over the file, so secrets can stay in the deployment env.
Ingest registers the extracted counterparty as a Sure merchant and attaches it — only where the payment type actually has one (card purchases, giro billers, Swish business; person-to-person Swish, own transfers and fees get none). Trailing store numbers are stripped so PRESSBYRAN 5121 and 5240 group as one merchant. Disable with SURE_MERCHANTS=false; upgrade pre-merchant history once with ingest -backfill-merchants.
SURE_LOAN_MAP=<merchant>=<sure-loan-uuid> mirrors expenses to that merchant into the loan account, and Sure's transfer matching links them as loan payments. The full payment reduces the balance (PSD2 has no interest/principal breakdown), so this only suits interest-free debts.
For interest-bearing loans, use amortizations in the config file instead: it posts one idempotent fixed principal entry per month from start onward, so history backfills itself. The entries are inflows on the loan, which Sure counts as income — pair each schedule with a Sure rule (Settings → Rules): name = <schedule name> → Exclude from budgeting and reports, effective at or before start. Don't use the account-level "exclude from reports" toggle — it also removes the loan from net worth.
See docker-compose.example.yml; build with make docker, run the interactive auth via docker compose run --rm --service-ports eb-ingest auth. Session state and raw dumps live under /data — keep that volume persistent. When consent expires, fetches fail with EXPIRED_SESSION/REVOKED_SESSION: re-run auth, then fetch -strategy longest (the bank only exposes >90 days of history right after fresh authentication).
Everything runs inside the devcontainer (.devcontainer/):
make up # create/start the devcontainer (needs @devcontainers/cli)
make check # formatting + linters + race tests + build
make build # bin/sure-eb-ingest