From raintree-technology-claude-starter
Integrates Plaid banking API for bank account connections via Plaid Link, Auth for ACH, Transactions, Balances, Identity verification, and webhooks in fintech apps.
npx claudepluginhub joshuarweaver/cascade-code-general-misc-2 --plugin raintree-technology-claude-starterThis skill is limited to using the following tools:
Plaid connects applications to users' bank accounts for financial data access, payments, and identity verification.
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Plaid connects applications to users' bank accounts for financial data access, payments, and identity verification.
| Product | Purpose |
|---|---|
| Auth | Bank account/routing numbers for ACH |
| Transactions | Transaction history (up to 24 months) |
| Identity | Verify user via bank account ownership |
| Balance | Real-time account balances |
| Investments | Holdings from investment accounts |
| Liabilities | Loan and credit card data |
npm install plaid react-plaid-link
import { Configuration, PlaidApi, PlaidEnvironments } from "plaid";
const client = new PlaidApi(
new Configuration({
basePath: PlaidEnvironments.sandbox,
baseOptions: {
headers: {
"PLAID-CLIENT-ID": process.env.PLAID_CLIENT_ID,
"PLAID-SECRET": process.env.PLAID_SECRET,
},
},
})
);
// POST /api/plaid/create-link-token
export async function POST(req: Request) {
const response = await client.linkTokenCreate({
user: { client_user_id: userId },
client_name: "Your App",
products: ["auth", "transactions"],
country_codes: ["US"],
language: "en",
});
return Response.json({ link_token: response.data.link_token });
}
import { usePlaidLink } from "react-plaid-link";
function ConnectBank({ linkToken }) {
const { open, ready } = usePlaidLink({
token: linkToken,
onSuccess: async (public_token, metadata) => {
// Exchange for access_token on server
await fetch("/api/plaid/exchange-token", {
method: "POST",
body: JSON.stringify({ public_token }),
});
},
});
return (
<button onClick={() => open()} disabled={!ready}>
Connect Bank Account
</button>
);
}
// POST /api/plaid/exchange-token
export async function POST(req: Request) {
const { public_token } = await req.json();
const response = await client.itemPublicTokenExchange({
public_token,
});
// Store access_token securely (encrypted in database)
await db.users.update(userId, {
plaid_access_token: response.data.access_token,
plaid_item_id: response.data.item_id,
});
return Response.json({ success: true });
}
const response = await client.authGet({ access_token });
const ach = response.data.numbers.ach[0];
console.log("Account:", ach.account);
console.log("Routing:", ach.routing);
const response = await client.transactionsGet({
access_token,
start_date: "2024-01-01",
end_date: "2024-12-31",
});
let transactions = response.data.transactions;
// Handle pagination
while (transactions.length < response.data.total_transactions) {
const more = await client.transactionsGet({
access_token,
start_date: "2024-01-01",
end_date: "2024-12-31",
offset: transactions.length,
});
transactions = transactions.concat(more.data.transactions);
}
Transaction object:
{
transaction_id: "abc123",
amount: 12.34, // Positive = outflow
date: "2024-11-16",
name: "Starbucks",
merchant_name: "Starbucks",
category: ["Food and Drink", "Coffee Shop"],
pending: false,
}
const response = await client.accountsBalanceGet({ access_token });
response.data.accounts.forEach((account) => {
console.log(`${account.name}: $${account.balances.current}`);
});
const response = await client.identityGet({ access_token });
const owner = response.data.accounts[0].owners[0];
console.log("Name:", owner.names[0]);
console.log("Email:", owner.emails[0].data);
console.log("Phone:", owner.phone_numbers[0].data);
// POST /api/plaid/webhook
export async function POST(req: Request) {
const { webhook_type, webhook_code, item_id } = await req.json();
switch (webhook_type) {
case "TRANSACTIONS":
if (webhook_code === "DEFAULT_UPDATE") {
// New transactions available - fetch them
await syncTransactions(item_id);
}
break;
case "ITEM":
if (webhook_code === "ERROR") {
// Connection issue - prompt user to re-authenticate
await notifyUserReauth(item_id);
}
break;
}
return Response.json({ received: true });
}
Key webhook events:
| Event | Meaning |
|---|---|
TRANSACTIONS: INITIAL_UPDATE | First batch ready |
TRANSACTIONS: DEFAULT_UPDATE | New transactions |
ITEM: ERROR | Connection issue |
ITEM: PENDING_EXPIRATION | Credentials expiring |
| Environment | Use Case | Base Path |
|---|---|---|
| Sandbox | Testing | PlaidEnvironments.sandbox |
| Development | Limited live (100 connections) | PlaidEnvironments.development |
| Production | Live | PlaidEnvironments.production |
user_goodpass_good1234When credentials expire:
// Create link token for update mode
const response = await client.linkTokenCreate({
user: { client_user_id: userId },
client_name: "Your App",
access_token: existingAccessToken, // Triggers update mode
country_codes: ["US"],
language: "en",
});
| Error | Solution |
|---|---|
ITEM_LOGIN_REQUIRED | Re-authenticate via Link update mode |
RATE_LIMIT_EXCEEDED | Implement exponential backoff |
PRODUCT_NOT_READY | Wait for webhook or retry |
DO:
DON'T:
// app/api/plaid/create-link-token/route.ts
import { NextResponse } from "next/server";
import { Configuration, PlaidApi, PlaidEnvironments } from "plaid";
const client = new PlaidApi(
new Configuration({
basePath: PlaidEnvironments.sandbox,
baseOptions: {
headers: {
"PLAID-CLIENT-ID": process.env.PLAID_CLIENT_ID!,
"PLAID-SECRET": process.env.PLAID_SECRET!,
},
},
})
);
export async function POST(req: Request) {
const session = await getSession();
const response = await client.linkTokenCreate({
user: { client_user_id: session.user.id },
client_name: "Your App",
products: ["auth", "transactions"],
country_codes: ["US"],
language: "en",
webhook: `${process.env.NEXT_PUBLIC_URL}/api/plaid/webhook`,
});
return NextResponse.json({ link_token: response.data.link_token });
}
plaid and react-plaid-link