Not comfortable with the terminal? Use the setup wizard at bridgebank.app — fill in your details, download two files, and run one command. No manual configuration needed.
Automatically sync your bank transactions to Actual Budget using Enable Banking.
- Imports confirmed transactions as cleared
- Imports pending transactions as uncleared -- categorise them immediately
- When a pending transaction confirms, it is automatically matched and cleared in place (no duplicates, your category is preserved)
- Email notification when your session is about to expire
Why Enable Banking? GoCardless (formerly Nordigen) stopped accepting new account registrations in July 2025. Enable Banking offers a free restricted tier that works for personal use.
- Docker and Docker Compose
- A free Enable Banking account
- A self-hosted Actual Budget instance
- Sign up at enablebanking.com
- Go to Applications and create a new application
- Download your private key (
private.pem) - Note your Application ID (a UUID shown on the dashboard)
git clone https://github.com/DAdjadj/bridge-bank.git
cd bridge-bankmkdir -p data
cp /path/to/your/private.pem data/private.pemInstall the dependencies and run the setup script:
pip install requests PyJWT cryptography
EB_APPLICATION_ID=your-app-id python3 dosetup.pyThe script will:
- Open an authorisation URL -- open it in your browser
- Log in to your bank and approve access
- Paste the redirect URL back into the terminal
- Save your session to
data/state.json
Note: when setting up your Enable Banking application, the redirect URL must be set to
https://enablebanking.com/
For banks other than Revolut:
EB_APPLICATION_ID=your-app-id \
EB_BANK_NAME="Monzo" \
EB_BANK_COUNTRY="GB" \
python3 dosetup.pySee Enable Banking's supported banks for supported banks and country codes.
Edit docker-compose.yml and fill in your values:
ACTUAL_URL: "http://actual-budget:5006"
ACTUAL_PASSWORD: "your-actual-password"
ACTUAL_SYNC_ID: "your-sync-id" # Settings > Show advanced settings > Sync ID
ACTUAL_ACCOUNT: "Revolut" # Name of the account in Actual Budget
EB_APPLICATION_ID: "your-app-id"Recommended: set ACCOUNT_HOLDER_NAME to your name as it appears on bank transfers (e.g. "John Doe,JOHN DOE"). This ensures incoming transfers and card refunds show the correct payee instead of your own name.
If Actual Budget is on a Docker network, uncomment the networks section and adjust to match your setup.
docker compose up -d
docker compose logs -fYou should see:
[INFO] Starting scheduler (every 6h)
[INFO] Starting sync...
[INFO] Fetched 12 transactions from Enable Banking
[INFO] Done: 12 added, 0 confirmed, 0 skipped
| Stage | What happens |
|---|---|
| Transaction appears as pending | Imported into Actual as uncleared |
| You categorise and rename it in Actual | Your changes are saved |
| Transaction confirms (usually 1-3 days) | Automatically flipped to cleared |
You can safely edit the category, payee, and notes on a pending transaction. Matching uses date and amount -- avoid changing either of those.
Enable Banking sessions expire after 180 days (a PSD2 requirement). You will receive an email notification 7 days before expiry if you configured SMTP.
To renew:
EB_APPLICATION_ID=your-app-id python3 dosetup.py
docker compose restart| Variable | Required | Default | Description |
|---|---|---|---|
ACTUAL_URL |
Yes | URL of your Actual Budget instance | |
ACTUAL_PASSWORD |
Yes | Actual Budget password | |
ACTUAL_SYNC_ID |
Yes | Sync ID from Actual Budget settings | |
ACTUAL_ACCOUNT |
No | Revolut |
Account name in Actual Budget |
EB_APPLICATION_ID |
Yes | Enable Banking application ID | |
SYNC_INTERVAL_HOURS |
No | 6 |
How often to sync |
ACCOUNT_HOLDER_NAME |
No | Your name as it appears on transfers, comma-separated. Used to correctly identify payees on incoming transfers and refunds. | |
NOTIFY_EMAIL |
No | Email address for session expiry alerts | |
SMTP_HOST |
No | SMTP server hostname | |
SMTP_PORT |
No | 587 |
SMTP server port |
SMTP_USER |
No | SMTP username | |
SMTP_PASS |
No | SMTP password (use app-specific password) |
Transactions not importing
- Check
docker compose logsfor errors - Verify your session is valid:
cat data/state.json-- checkeb_session_expiry - If expired, re-run
dosetup.py
Incoming transfers showing your own name as payee
- Set
ACCOUNT_HOLDER_NAMEindocker-compose.ymlto your name as it appears on bank transfers
Duplicate transactions
- Should not happen with the current setup
- If you see duplicates, open an issue
Session expired
- Re-run
dosetup.pyand restart the container
Container keeps restarting
- Usually means Actual Budget is unreachable -- verify
ACTUAL_URLand that the container is running
Transaction skipped with "Multiple rows were found when one or none was required"
- This means Actual Budget has duplicate payee entries with the same name
- Go to Settings > Payees in Actual Budget, search for the payee name shown in the log, and merge the duplicates
- Restart the container after merging -- the skipped transaction will be retried automatically on the next sync
Transaction skipped with "Multiple rows were found when one or none was required"
- This means Actual Budget has duplicate payee entries with the same name
- Go to Settings > Payees in Actual Budget, search for the payee name shown in the log, and merge the duplicates
- Restart the container after merging -- the skipped transaction will be retried automatically on the next sync
Enable Banking API
|
| (every 6 hours)
v
sync.py
|
|-- PDNG transactions --> Actual Budget (uncleared)
| |
| You categorise
|
|-- BOOK transactions --> match pending by date+amount
|
flip to cleared
update payee name
Enable Banking acts as a PSD2-compliant bridge between your bank and this script. Your bank credentials never leave Enable Banking -- this script only receives a session token.
Any bank supported by Enable Banking should work. Full list here: https://enablebanking.com/open-banking-apis
Tested with:
- Revolut (PT, GB)
If you test with another bank, please open a PR to add it to this list.
MIT