Connecting an app to your bank looks like an API problem. It is really a trust problem and an assumptions problem. I learned both the hard way: one aggregator wanted to hold my bank password, and one bank looked like it was blocking me when the real problem was sitting in my own config.
From files to an aggregator
QuidDash, my first run at this, lived on files. Every few days I logged into each bank, downloaded a transaction export, and uploaded it by hand. No balances, no automation, and it only worked on the laptop in front of me. For QuidXy I wanted the opposite: one integration that talks to every bank for me, so the data is just there when I open the page.
That one integration is called an aggregator. You have used Plaid even if you have never heard of it; it is the pipe behind a huge share of the finance apps on your phone. The detail that sold me was its OAuth flow: I authorize my bank to share data with my app, and my bank username and password never pass through the middleman. The credentials stay between me and the bank. That is the right shape for something holding my money data.
So I wired up Plaid first. And my first real connection failed. The AI I was pairing with had a confident explanation: my bank was probably limited to Plaid's Enterprise tier, not available on a hobbyist account. It sounded plausible. I half-believed it and went looking for an alternative. Hold that thought, because the explanation was wrong and believing it cost me time.
The aggregator that wanted my password
The alternative was Teller. Their sandbox was clean and it worked on the first try, which after a failed Plaid attempt felt great. I even built the second integration behind an aggregator-agnostic data model so the app didn't care which pipe a connection came through.
Then I read how Teller actually links a real bank: it stores your bank login credentials on their servers. Your username and your password, sitting in their database, replayed to the bank on your behalf.
A third party warehousing my bank password fails the security bar before I write a line of code. The sandbox working was never the question. The trust model was.
So I kept the abstraction I had built and deleted the Teller integration behind it. Back to Plaid, where the real problem was still waiting.
The bank that kept disappearing
Here is the symptom: I would open the Plaid link flow, and Capital One either wouldn't connect or wouldn't show up the way I expected. Plaid's own message was some flavor of connectivity not supported.
This is where the wrong assumption nearly won. Some banks really do gate access per customer, and the AI had already planted "your bank is restricted" in my head. The easy story was that Capital One had decided I wasn't worth supporting. That story required nothing from me. It was also wrong.
When I stopped blaming the bank and actually read the config, it was two unrelated problems wearing one error message.
One: OAuth needs a registered redirect URI. Capital One requires the OAuth flow, and OAuth needs a redirect URL that is registered in the Plaid dashboard and passed into link_token_create. I had neither. Without them, the flow can't hand control back after the bank login, so it just fails.
Two: Plaid drops banks that don't support every product you request. When I ported the QuidDash setup over, I was asking for two products at once:
Capital One doesn't offer the investments product. And Plaid's rule is all-or-nothing: if an institution can't satisfy every product in your request, it is dropped from the picker entirely. So by asking for investments, I was silently excluding every bank that doesn't do investments, Capital One included. The bank wasn't rejecting me. My request was rejecting the bank.
The fix was small, and a little embarrassing in its smallness:
Plus a redirect handler and the registered URI so OAuth could complete. Capital One connected.
One integration, two intents
Two products fighting over one link flow is a design smell, so I split the intent instead of just deleting investments. A bank link asks for [transactions]. An investment link asks for [transactions, investments]. Same Plaid integration, two intents, so a transactions-only bank like Capital One and an investments bank like Fidelity can both connect without stepping on each other.
The honest footnote: I later removed the Investments view from QuidXy as a scope decision, so some of that investment plumbing is dormant now. I would still split it the same way. The cost was one extra code path. The alternative was a link flow that quietly excludes half the banks in the country.
The part worth keeping
The bug was config. The lesson was about assumptions. A "smart" explanation I didn't verify, the bank is Enterprise-only, sent me on a multi-day detour through a second aggregator I was never going to ship. The actual cause was sitting in my own product list the whole time.
AI did the typing and made a useful sounding board, but it was also confidently wrong, and the only thing that fixed it was opening the config and reading it like I meant it.
The bank is connected now, which means there is an access token to protect. The next field note is the part I am proudest of: how QuidXy keeps that token, and everything else, encrypted at rest and reachable from the internet without opening a single port.