SimpleFIN starts with a narrow question: what is the smallest useful contract between a person’s financial-data provider and the app they choose? Its own analogy is “RSS for financial data.” An application can read accounts, balances, transactions and, where supplied, holdings without receiving the user’s bank password or gaining payment authority.
Protocol, provider and app are separate layers
The protocol defines the setup-token exchange and JSON shape. A provider maintains bank connections and serves the protocol. An application claims a connection, stores its access URL and imports the data. SimpleFIN Bridge is one provider; BankSync can also deliver supported financial data to SimpleFIN-compatible destinations.
This separation lets a hobbyist app focus on budgeting, reporting or automation while the provider handles bank connectivity and the user keeps control of a revocable, read-only connection.

Setup tokens and access URLs
A setup token is Base64-encoded transport for a one-time claim URL. The application decodes it and sends one POST request. The response is the long-lived access URL. The setup token should then be discarded.
The access URL contains credentials. Anyone holding it can read the data exposed by that connection, so treat it like a password: encrypt it at rest, keep it server-side, remove it from logs and never paste it into analytics or support tickets. Future reads append /accounts and bounded query parameters.
What /accounts returns
The response groups accounts beneath a provider connection and supplies stable IDs, names, currency, balances and transaction records. Transactions have a provider ID, posting time, amount and description, with optional pending and extra metadata. Version 2 uses structured errlist, renames Organization to Connection, and adds balances-only=1 plus account= filters.
SimpleFIN is pull-based, not webhook-based. The Bridge guide expects no more than about 24 requests per day, recommends jitter away from the top of the hour, limits requested windows to 90 days, and suggests a recent overlap window so late-posting records are caught.
The complete connection lifecycle
Connect institutions at the provider
The user authorises bank access with the provider rather than sharing credentials with the budgeting app.
Create a setup token
Generate one connection for one application.
Claim the token once
Decode, POST, receive the access URL and discard the setup token.
Store the access URL securely
Encrypt it and keep it out of the browser and logs.
Fetch a bounded account window
Read `/accounts`, parse errors and normalize records.
Reconcile idempotently
Map accounts, deduplicate by stable transaction ID and overlap recent days.
Why builders like the narrow design
There is no bank-by-bank SDK in the app, no payment permission and no need to invent a new import contract. Ordinary HTTP and JSON are enough. The hard parts are familiar engineering work: secret storage, signed amounts, idempotency, account mapping, time zones and honest failure states.
It works especially well for local-first dashboards, envelope budgets, ledger importers and personal automation. It is not the right primitive for instant card events, payment initiation, identity verification or guaranteed real-time data.
BankSync’s role
BankSync is useful when bank coverage comes from regional open-banking rails or other supported connectors while the destination application expects SimpleFIN. Compatibility describes the delivery contract; it does not mean every bank and account type is automatically supported. Verify coverage before promising a specific institution.
Builder checklist
| Area | Good default | Why |
|---|---|---|
| Secret storage | Server-side encryption and URL redaction | The access URL authorises reads |
| Sync cadence | Daily or a few times daily with jitter | Respects provider quotas |
| History | Bounded request plus rolling overlap | Captures late-posting records |
| Deduplication | Stable provider transaction ID | Avoids collapsing recurring charges |
| Health | Last success, source freshness and structured errors | Prevents stale data looking current |
A small protocol still deserves production discipline.
First-principles recommendation
Build a manual sync before a scheduler. Make one account map correctly before importing every account. Preserve raw provider fields beside normalized data. Give the user a disconnect path that does not erase their local history. Those choices make the integration understandable when a bank, provider or app changes later.