fintual-api is a one-shot worker that logs into Fintual, fetches investment performance data, and imports the resulting variation transactions into Actual Budget.
The repo intentionally supports only two flows:
pnpm onceruns the full sync once- unattended 2FA retrieval via Gmail IMAP + app password
- Node.js 24+
- pnpm
- Fintual credentials
- Actual Budget server credentials
- Gmail app password for unattended 2FA
- Install dependencies:
pnpm install- Create a local env file:
cp .env.example .env- Fill in your Actual, Fintual, and Gmail values.
Enable 2-Step Verification in Google Account settings, then create a Gmail app password for the mailbox used to receive Fintual 2FA emails.
- Open the 2-Step Verification page (you only need to do this once): 2-Step Verification
- Open App Passwords directly: App Passwords
- Create an app password for any label (for example:
fintual-api), then copy the generated 16-character password. - Paste it into
.envasGMAIL_APP_PASSWORD.
If you prefer a quick copy/paste terminal flow:
read -s "GMAIL_APP_PASSWORD?Paste Gmail app password: "; echo
cat >> .env <<EOF
GMAIL_USER_EMAIL=your@gmail.com
GMAIL_APP_PASSWORD=$GMAIL_APP_PASSWORD
GMAIL_IMAP_HOST=imap.gmail.com
GMAIL_IMAP_PORT=993
EOF
unset GMAIL_APP_PASSWORDSet these values in .env (or your runtime secret manager):
GMAIL_USER_EMAILGMAIL_APP_PASSWORDGMAIL_IMAP_HOST(default:imap.gmail.com)GMAIL_IMAP_PORT(default:993)
fintual-api polls IMAP over TLS and extracts the 6-digit code from matching emails.
Use gcloud CLI to store and retrieve the Gmail app password without committing it:
gcloud secrets create fintual-gmail-app-password --replication-policy=automatic
printf '%s' "$GMAIL_APP_PASSWORD" | gcloud secrets versions add fintual-gmail-app-password --data-file=-
gcloud secrets versions access latest --secret=fintual-gmail-app-passwordExample to materialize a local .env value from Secret Manager:
echo "GMAIL_APP_PASSWORD=$(gcloud secrets versions access latest --secret=fintual-gmail-app-password)" >> .envOptionally type-check the project, then run the sync directly from the TypeScript source:
pnpm typecheck
pnpm onceThe worker will:
- log in to Fintual over HTTP (
initiate_login→ Gmail IMAP 2FA when required →finalize_login_web) and fetch GraphQL performance data - write the result to
tmp/fintual-data/balance-2.json - import variation transactions into Actual Budget
To capture login and GraphQL traffic for analysis (HAR), use agent-browser ≥ 0.22 and run:
pnpm capture:harDetails and observed endpoints are in docs/fintual-http-capture.md. Output goes to tmp/fintual-capture.har (gitignored).
The published container image is designed as a one-shot worker. Its default command runs the sync once:
docker run --rm --env-file .env docker.io/samaluk/fintual-api:latestMount ./tmp if you want to inspect the generated files locally:
docker run --rm --env-file .env -v "$(pwd)/tmp:/app/tmp" docker.io/samaluk/fintual-api:latestThe local compose file keeps the worker container idle so you can run the sync manually with the exact same Docker environment each time:
docker compose --env-file .compose.env up -d --build
docker exec -it fintual-api-local ./bin/run-sync.shThe compose stack also starts ofelia, so you can test the scheduled job-exec path and inspect scheduler logs:
docker logs -f fintual-api-ofeliaSet OFELIA_SYNC_SCHEDULE in .compose.env if you want a faster local test cadence, for example:
OFELIA_SYNC_SCHEDULE=@every 5mIf you keep runtime secrets in Secret Manager, fetch GMAIL_APP_PASSWORD at deploy time and inject it into the homelab runtime env instead of storing it in compose files.
Useful commands while debugging:
docker logs -f fintual-api-local
docker logs -f fintual-api-ofelia
docker exec -it fintual-api-local sh
docker exec -it fintual-api-local ./bin/run-sync.sh
docker compose --env-file .compose.env downThis repo publishes docker.io/samaluk/fintual-api from GitHub Releases.
Repository secrets required for publishing:
DOCKERHUB_USERNAMEDOCKERHUB_TOKEN
Published tags:
sha-<commit>latestfrom GitHub Releases- the GitHub Release tag itself, such as
v1.0.0
The intended production model is:
- GitHub Actions publishes the worker image to Docker Hub
- the homelab compose stack pulls the image
- a long-lived idle worker keeps secrets in its runtime environment
- Ofelia schedules
job-execruns inside that worker - Komodo deploys compose changes from the homelab repo
Recommended Ofelia schedule for Santiago, Chile weekdays at 21:00:
- cron:
0 21 * * 1-5 - timezone:
America/Santiago
Use an idle fintual-api worker plus an ofelia service that executes ./bin/run-sync.sh on schedule.
For homelab deployments, store GMAIL_APP_PASSWORD in your secret manager (for example, GCP Secret Manager) and inject it into the worker environment at runtime.