You do not need to become a bank-data company to build a useful finance tool. Let BankSync handle supported bank connectivity and expose a SimpleFIN-compatible feed. Your app can concentrate on a small read-only importer, durable local storage and the personal workflow you actually want.
The best first version is boring: connect, map, sync, reconcile and export. Budgets, charts and AI come later.

Draw the trust boundary
Keep the setup-token claim and every /accounts call in a backend or local trusted process. The browser should never see the access URL. Store the credential encrypted, with the key outside the database. If the app is single-user, a local secret store is sufficient; if hosted, separate keys and data by tenant.
Use a small domain model
You need a connections table, an accounts table and a transactions table. Store remote IDs, provider, currency and raw source JSON beside normalized values. Give transactions a unique constraint on connection + remote transaction ID. Add a sync_runs table when you need observability, not before.
Build order
Implement token claim
Decode Base64, validate HTTPS, POST once and store the access URL securely.
Fetch synthetic accounts
Use demo data before connecting a real institution.
Persist remote accounts
Keep stable IDs and require explicit local mapping.
Normalize transactions
Preserve original timestamp, amount, currency, pending state and raw data.
Make import idempotent
Upsert by provider ID and test repeated fixtures.
Add bounded history and overlap
Request no more than 90 days and re-read recent days.
Schedule with jitter
Stay under provider quotas and separate attempt from success.
Add export and disconnect
Users should be able to leave without losing their local history.
Sign conventions and money types
Never use binary floating point for money. Store decimal strings or integer minor units with the currency. Do not assume a negative amount always means “bad”: source conventions and account types matter. Define one canonical sign model and test checking, credit card, transfer and refund fixtures.
Pending transactions
Pending records can disappear, change IDs or become posted depending on provider behavior. Preserve the pending flag and use a recent overlap. If the provider offers a stable ID across state changes, upsert it; otherwise create a deliberate matching policy and surface uncertainty.
Observability without leaking data
Record connection ID, requested window, HTTP status, record counts, structured error code, duration and timestamps. Do not record the access URL or full transaction descriptions. A redacted diagnostic export is more useful than a giant raw log.
Minimal production controls
| Stage | What to do | Check |
|---|---|---|
| Secrets | Encrypt access URL and redact logs | No credential in browser |
| Data model | Unique remote transaction ID | Repeat import is a no-op |
| History | Bounded range plus overlap | Late posts are caught |
| Scheduling | Jitter and backoff | Provider quota respected |
| Errors | Parse `errlist` and show stale state | No false success |
| Portability | Export and disconnect | Low exit cost |
Small projects can still be robust.
Add AI last
If you expose the local database to Claude or another assistant, create a read-only query surface over a derived schema. Keep the model out of the import write path. Deterministic categorization rules are easier to audit; an assistant can suggest categories without mutating the ledger.
A weekend milestone
By Sunday evening, aim for: one claimed token, one mapped account, a 30-day import, a second zero-change re-import, a redacted sync report and a CSV export. That is a useful system. Everything after it is product design.