Synchronise investment or portfolio balances into Actual Budget. Fetches prices from supported providers, aggregates per portfolio, and posts adjustment transactions on a schedule or via the Web UI.
- Provider-agnostic fetch layer (AlphaVantage, Finnhub, TwelveData) with an extensible adapter pattern.
- Web UI for mapping symbols → portfolios → Actual accounts and triggering manual syncs, designed to sit behind a shared forward-auth proxy such as
actual-auto-auth. - Cron-driven daemon with optional headful Puppeteer mode for debugging provider flows.
- Docker image with baked-in health check and persistent data volume.
- CSV export (positions or holdings aggregated by stock) for loading snapshots into spreadsheets such as Google Sheets.
- Node.js ≥ 22.
- Actual Budget server credentials (
ACTUAL_SERVER_URL,ACTUAL_PASSWORD,ACTUAL_SYNC_ID). - Provider API keys (AlphaVantage, Finnhub, TwelveData) depending on the sources you enable.
git clone https://github.com/rjlee/actual-investment-sync.git
cd actual-investment-sync
npm installOptional git hooks:
npm run preparecp .env.example .env
docker build -t actual-investment-sync .
mkdir -p data/budget
docker run -d --env-file .env \
-p 3000:3000 \
-v "$(pwd)/data:/app/data" \
actual-investment-sync --mode daemon --uiPublished images live at ghcr.io/rjlee/actual-investment-sync:<tag> (see Image tags).
Prefer Docker Compose? Two samples are included:
docker-compose.yml– exposes the Web UI directly onHTTP_PORT.docker-compose.with-auth.yml.example– bundles Traefik plusactual-auto-authso the UI is protected by the shared password prompt; copy it todocker-compose.yml, adjustAUTH_APP_NAME/AUTH_COOKIE_NAME, and ensureACTUAL_PASSWORDis provided so the auth sidecar can sign cookies.
.env– primary configuration, copy from.env.example.config.yaml/config.yml/config.json– optional defaults, copy fromconfig.example.yaml.
Precedence: CLI flags > environment variables > config file.
| Setting | Description | Default |
|---|---|---|
DATA_DIR |
Local storage for mappings and cached data | ./data |
BUDGET_DIR |
Budget cache directory | ./data/budget |
SYNC_CRON / SYNC_CRON_TIMEZONE |
Daemon cron schedule | 45 * * * * / UTC |
DISABLE_CRON_SCHEDULING |
Disable cron while in daemon mode | false |
HTTP_PORT |
Enables Web UI when set or --ui passed |
3000 |
AUTH_COOKIE_NAME |
Cookie name forwarded by Traefik for logout UI | actual-auth |
LOG_LEVEL |
Pino log level | info |
ALPHAVANTAGE_API_KEY |
API key for AlphaVantage provider | unset |
FINNHUB_API_KEY |
API key for Finnhub provider | unset |
TWELVEDATA_API_KEY |
API key for TwelveData provider | unset |
ENABLE_NODE_VERSION_SHIM |
Legacy shim for older @actual-app/api checks |
false |
INVESTMENT_API_TOKEN |
Bearer token for API authentication | unset (disabled) |
- One-off sync:
npm run sync - Daemon with UI:
npm run daemon -- --ui --http-port 3000 - Disable cron in daemon:
DISABLE_CRON_SCHEDULING=true npm run daemon
Visit http://localhost:3000 (or your configured port) to map portfolios, update credentials, and trigger manual syncs. In production, place the UI behind a forward-auth proxy (for example the shared actual-auto-auth service) so access is password protected.
The Web UI server exposes REST endpoints under /api/:
| Endpoint | Method | Description |
|---|---|---|
/api/data |
GET | Returns stocks, portfolios, accounts |
/api/budget-status |
GET | Returns budget download status |
/api/mappings |
POST | Saves stock/portfolio mappings |
/api/sync |
POST | Triggers a sync operation |
/api/export |
GET | Downloads CSV export (positions/holdings) |
/api/export/positions |
GET | Returns positions data as JSON |
/api/export/holdings |
GET | Returns holdings aggregated by stock as JSON |
API endpoints support optional bearer token authentication. Set the INVESTMENT_API_TOKEN environment variable to enable:
INVESTMENT_API_TOKEN=your-secret-tokenBearer token auth is required for these endpoints:
/api/data- Returns stocks, portfolios, accounts/api/export- Downloads CSV export/api/export/positions- Returns positions data as JSON/api/export/holdings- Returns holdings aggregated by stock as JSON
curl -H "Authorization: Bearer your-secret-token" http://localhost:3000/api/dataIf INVESTMENT_API_TOKEN is not set, these endpoints are publicly accessible.
docker run --rm --env-file .env \
-p 3000:3000 \
-v "$(pwd)/data:/app/data" \
ghcr.io/rjlee/actual-investment-sync:latest --mode daemon --uinpm test
npm run lint
npm run lint:fix
npm run format
npm run format:checkghcr.io/rjlee/actual-investment-sync:<semver>– pinned to a specific@actual-app/apirelease.ghcr.io/rjlee/actual-investment-sync:latest– highest supported API version.
See rjlee/actual-auto-ci for tagging policy and automation details.
MIT © contributors.