This project provides an importer from Israeli banks (via israeli-bank-scrapers) into Actual Budget.
-
Multi Bank Support
Supports all of the institutions that the israeli-bank-scrapers library covers (Bank Hapoalim, Cal, Leumi, Discount, etc.). -
Prevents duplicate transactions
Uses Actual’simported_idlogic. -
Automatic Account Creation
If the bank account does not exist in Actual, it will be created automatically. -
Reconciliation
Optional reconciliation to adjust account balances automatically. -
Credit Card / Multi-Account Mapping (Targets)
Supports mapping multiple scraped accounts/cards into one Actual account, or mapping each scraped card into its own Actual account (viatargetsandaccounts). -
Concurrent Processing
Uses a queue (via p-queue) to manage scraping tasks concurrently. -
Pending transactions are skipped
Only completed transactions are imported into Actual, which avoids provisional card amounts and pre-conversion foreign-currency rows landing in the final ledger.
https://hub.docker.com/r/tomerh2001/israeli-banks-actual-budget-importer
services:
importer:
image: tomerh2001/israeli-banks-actual-budget-importer:latest
restart: always
cap_add:
- SYS_ADMIN
environment:
- TZ=Asia/Jerusalem
- SCHEDULE=0 0 * * * # Optional (Used to run periodically - remove to run once)
volumes:
- ./config.json:/app/config.json
- ./cache:/app/cache # Optional
- ./chrome-data:/app/chrome-data # Optional (Used to solve 2FA issues like with hapoalim)This importer bundles a specific @actual-app/api version, and Actual versions
the API package and the server together.
If you upgrade your Actual server, upgrade this importer image as well. A stale
importer image against a newer Actual server can fail during startup with
errors such as out-of-sync-migrations.
The importer now checks the server version before downloading the budget and fails early with a clear mismatch message that includes both versions.
The application configuration is defined using JSON and validated against a schema.
The main configuration file is config.json.
The configuration has two independent top-level sections:
actual– Configures the Actual Budget connection.banks– Configures bank scrapers and account mappings.
This section configures the connection to your Actual Budget server and budget.
It is always required, regardless of how you configure banks or targets.
{
"actual": {
"init": {
"dataDir": "./data",
"password": "your_actual_password",
"serverURL": "https://your-actual-server.com"
},
"budget": {
"syncId": "your_sync_id",
"password": "your_budget_password"
}
}
}Nothing in this block changes when using targets, credit cards, or multi-account mappings.
actual.init is passed directly to @actual-app/api.
- Use
passwordfor the standard Actual server password flow. - Use
sessionTokeninstead ofpasswordif you authenticate to Actual via a session token. - If your Actual server uses OpenID/OAuth, the provider credentials belong in the Actual server configuration, not in this importer's
config.json.
Example with sessionToken:
{
"actual": {
"init": {
"dataDir": "./data",
"sessionToken": "your_actual_session_token",
"serverURL": "https://your-actual-server.com"
},
"budget": {
"syncId": "your_sync_id",
"password": "your_budget_password"
}
}
}The banks section defines:
- Which banks to scrape
- The credentials for each bank
- How scraped accounts/cards are mapped into Actual accounts
Each bank entry includes the credentials required by israeli-bank-scrapers
(e.g. userCode, username, password, etc.).
For oneZero, config.json can use a long-term OTP token:
{
"banks": {
"oneZero": {
"email": "you@example.com",
"password": "your_one_zero_password",
"otpLongTermToken": "your_long_term_token",
"actualAccountId": "actual-account-id"
}
}
}The importer uses a JSON config file, so the practical One Zero setup here is
the otpLongTermToken flow rather than a runtime callback like
otpCodeRetriever.
chrome-datapersists Chromium session data and helps avoid repeated OTP prompts after an interactive login.SHOW_BROWSER=trueis only for interactive debugging. It launches Chromium in the container process, but this project does not expose a built-in web UI or remote desktop for that browser.- On a headless host, the usual pattern is to keep
chrome-datapersistent and only bring up a graphical environment when you need to refresh a bank session manually. - The importer currently asks bank scrapers for the last 2 years of data.
A single bank scrape (for example visaCal) may return multiple accounts/cards.
Different users model these differently in Actual, so the importer supports targets.
Each target represents:
- One Actual account
- One or more scraped accounts/cards that feed into it
For each target:
- Imported transactions = concatenation of transactions from selected cards
- Reconciliation (if enabled) = sum of balances of selected cards
(only cards with a valid numeric balance are included)
- Reconciliation is controlled by the
reconcileboolean. - When
reconcile: true, a new reconciliation transaction is created on every run (no updates, no reconciliation). - Existing reconciliation transactions are never modified or reused.
- If
reconcileis omitted or set tofalse, no reconciliation transaction is created.
{
"actual": {
"init": {
"dataDir": "./data",
"password": "your_actual_password",
"serverURL": "https://your-actual-server.com"
},
"budget": {
"syncId": "your_sync_id",
"password": "your_budget_password"
}
},
"banks": {
"visaCal": {
"username": "bank_username",
"password": "bank_password",
"targets": [
{
"actualAccountId": "actual-creditcards-all",
"reconcile": true,
"accounts": "all"
}
]
}
}
}{
"actual": {
"init": {
"dataDir": "./data",
"password": "your_actual_password",
"serverURL": "https://your-actual-server.com"
},
"budget": {
"syncId": "your_sync_id",
"password": "your_budget_password"
}
},
"banks": {
"visaCal": {
"username": "bank_username",
"password": "bank_password",
"targets": [
{
"actualAccountId": "actual-card-8538",
"reconcile": true,
"accounts": ["8538"]
},
{
"actualAccountId": "actual-card-7697",
"reconcile": true,
"accounts": ["7697"]
}
]
}
}
}{
"actual": {
"init": {
"dataDir": "./data",
"password": "your_actual_password",
"serverURL": "https://your-actual-server.com"
},
"budget": {
"syncId": "your_sync_id",
"password": "your_budget_password"
}
},
"banks": {
"visaCal": {
"username": "bank_username",
"password": "bank_password",
"targets": [
{
"actualAccountId": "actual-cal-primary",
"reconcile": true,
"accounts": ["8538", "7697"]
}
]
}
}
}This configuration style is fully supported for backward compatibility,
but does not allow fine-grained control over multiple cards/accounts.
It maps all scraped accounts from the bank into a single Actual account.
{
"actual": {
"init": {
"dataDir": "./data",
"password": "your_actual_password",
"serverURL": "https://your-actual-server.com"
},
"budget": {
"syncId": "your_sync_id",
"password": "your_budget_password"
}
},
"banks": {
"hapoalim": {
"actualAccountId": "account-123",
"userCode": "bank_user",
"password": "bank_password",
"reconcile": true
},
"leumi": {
"actualAccountId": "account-456",
"username": "bank_username",
"password": "bank_password"
}
}
}- The
actualblock is always required and independent of bank configuration. targetsare optional but strongly recommended for credit-card providers.- Duplicate transactions are prevented using a stable
imported_id. - Credit card balances are often negative; reconciliation uses the values as returned by the bank.
This project is open-source. Please see the LICENSE file for licensing details.
- israeli-bank-scrapers: Thanks to the contributors of the bank scraper libraries.
- Actual App: For providing a powerful budgeting API.
- Open-source Community: Your support and contributions are appreciated.