← All projects

Discord Bot: Bridging AI and Financial Data

Cover Image for Discord Bot: Bridging AI and Financial Data

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

Let's build something together.

Got an idea? I'm always up for a new challenge, whether it's a side project, a startup, or something in between.

© 2026 Cyrus David Pastelero. All rights reserved.

Built with Next.js, TypeScript & Tailwind CSS