The problem
Financial apps give you data, but they make you work for it. Want to know how much you spent on food last month? Open the app, navigate to transactions, set filters, scroll through results. Want to compare it to the previous month? Start over.
I thought: what if you could just ask? "How much did I spend on restaurants in November?" and get an answer instantly, right in Discord where I already spend time.
What I built
A Discord bot that combines OpenAI's GPT-4 with Plaid's financial APIs. You chat naturally, and the bot figures out which Plaid endpoints to call based on what you're asking. It handles account balances, transaction history, spending insights, and more.
How it works
The interesting part is how GPT-4 decides which API to call. The bot doesn't use rigid keyword matching. Instead, it sends your question to GPT-4 along with descriptions of available Plaid functions. GPT-4 picks the right one and extracts parameters from your natural language query.
Tech stack
| Layer | Technology | Why |
|---|---|---|
| Bot Framework | discord.js | Most mature Discord library |
| AI | OpenAI GPT-4 | Function calling capability |
| Financial Data | Plaid | Bank data without the scraping |
| Language | TypeScript | Type safety for API responses |
What building this taught me
1. Function calling changes everything
Before function calling, I was trying to parse user intent with regex and keyword matching. It was brittle and frustrating.
OpenAI's function calling feature made the bot actually understand what users wanted. You describe your available functions, and GPT-4 decides which one to call with the right parameters.
// Define available functions for GPT-4
const functions = [
{
name: 'get_transactions',
description: "Get user's transactions for a date range",
parameters: {
type: 'object',
properties: {
start_date: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
end_date: { type: 'string', description: 'End date (YYYY-MM-DD)' },
category: { type: 'string', description: 'Optional category filter' },
},
},
},
]
// GPT-4 automatically extracts: "last week" → actual dates
// "biggest purchases" → sort by amount descending
Resources:
2. Per-user financial data was the thing I never actually built
I used to describe this section as strict per-user permissions, with a getPlaidToken(userId) lookup so
each person only reached their own bank data. No such function exists. PlaidService reads one token
from config and every call in the class uses it:
// src/plaid/plaid.service.ts
this.accessToken = ACCESS_TOKEN // one token, from env, for everyone
async getTransactions(startDate?: string, endDate?: string) {
return this.client.transactionsGet({ access_token: this.accessToken, ... })
}
There is no per-user token storage anywhere in the repo, and the only place interaction.user.id appears
is a developer allowlist. Every user who queries the bot reads the same linked account. The saving grace
is that it points at PlaidEnvironments.sandbox, so the shared account was never real money.
That is the honest version: a single-tenant prototype. Multi-user financial data is the interesting problem, and calling it solved was how I avoided noticing I had not started it.
Resources:
3. Discord.js has hidden complexity
The library is powerful but the documentation assumes you already know Discord's permission model. I spent hours debugging why commands weren't registering, only to find it was a permission scope issue.
The slash command registration process is particularly tricky. Guild commands update instantly but global commands take up to an hour.
Resources:
4. Rate limits compound when you chain APIs
Both OpenAI and Plaid have rate limits. When you chain them together, you need to handle both. I added request queuing and graceful degradation when limits are hit.
// Simple rate limiter
const rateLimiter = {
openai: new Bottleneck({ maxConcurrent: 3, minTime: 200 }),
plaid: new Bottleneck({ maxConcurrent: 5, minTime: 100 })
};
// Wrap API calls
const response = await rateLimiter.openai.schedule(() =>
openai.chat.completions.create(...)
);
Resources:
The bigger realization
Building this bot taught me that AI is best when it's invisible. Users don't want to learn prompts or understand how the AI works. They just want to ask questions and get answers.
The magic isn't the AI. It's that you can talk to your bank account like a person.
What I would do differently
Cache more aggressively. Every question triggers API calls, which adds latency and cost. Many financial queries could use cached data that's a few minutes old without losing usefulness.
References
Links
- GitHub: financial-discord-bot
