Agent skill
plaid-expert
Expert guidance for Plaid banking API integration including Link, Auth, Transactions, Identity, and webhook handling. Invoke when user mentions Plaid, bank connections, financial data, ACH, or banking APIs.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/plaid
SKILL.md
Plaid Integration Expert
Purpose
Provide comprehensive guidance for integrating Plaid's financial data APIs to connect bank accounts, retrieve transactions, verify identities, and process ACH transfers.
When to Use
Invoke when user mentions:
- "Plaid" or "banking API"
- "Link" (Plaid Link flow)
- "bank account connection" or "financial data"
- "ACH transfers" or "bank-to-bank payments"
- "transaction history" or "account balance"
- "income verification" or "identity verification"
Core Products
1. Auth
Purpose: Retrieve bank account and routing numbers for ACH, wire transfers, and bank-to-bank payments.
Use cases:
- ACH payment processing
- Wire transfers
- Bank account verification
2. Transactions
Purpose: Access transaction history for budgeting, expense tracking, and financial insights.
Features:
- Up to 24 months of history
- Categorized transactions
- Merchant information
- Pending and posted transactions
3. Identity
Purpose: Verify user identity through bank account ownership.
Data retrieved:
- Account holder names
- Email addresses
- Phone numbers
- Physical addresses
4. Balance
Purpose: Real-time account balance checking to prevent payment failures.
Use cases:
- Insufficient funds detection
- Payment preflight checks
- Account monitoring
5. Investments
Purpose: Access holdings and transactions from investment accounts.
Coverage:
- Stocks, bonds, ETFs
- 401(k), IRA, brokerage accounts
- Real-time valuations
6. Liabilities
Purpose: Loan and credit data access.
Coverage:
- Student loans
- Mortgages
- Credit cards
- Auto loans
Plaid Link Integration
What is Link?
Plaid Link is the client-side component that users interact with to securely connect their bank accounts. It's a modal/iframe that handles the entire authentication flow.
Link Flow
- User clicks "Connect Bank Account"
- Link modal opens
- User searches for their bank
- User enters credentials (or OAuth)
- User selects accounts to link
- Link returns a
public_token - Exchange
public_tokenforaccess_tokenserver-side
Frontend Integration (React)
Install:
npm install react-plaid-link
Implementation:
import { usePlaidLink } from 'react-plaid-link';
function PlaidLinkButton() {
const [linkToken, setLinkToken] = useState(null);
// 1. Create link token (call your backend)
useEffect(() => {
async function createLinkToken() {
const response = await fetch('/api/plaid/create-link-token', {
method: 'POST',
});
const data = await response.json();
setLinkToken(data.link_token);
}
createLinkToken();
}, []);
// 2. Configure Link
const { open, ready } = usePlaidLink({
token: linkToken,
onSuccess: async (public_token, metadata) => {
// 3. Exchange public token for access token
await fetch('/api/plaid/exchange-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ public_token }),
});
},
onExit: (err, metadata) => {
if (err) console.error(err);
},
});
return (
<button onClick={() => open()} disabled={!ready}>
Connect Bank Account
</button>
);
}
Backend: Create Link Token
Node.js example:
const plaid = require('plaid');
const client = new plaid.PlaidApi(
new plaid.Configuration({
basePath: plaid.PlaidEnvironments.sandbox,
baseOptions: {
headers: {
'PLAID-CLIENT-ID': process.env.PLAID_CLIENT_ID,
'PLAID-SECRET': process.env.PLAID_SECRET,
},
},
})
);
// Create link token
app.post('/api/plaid/create-link-token', async (req, res) => {
const request = {
user: {
client_user_id: req.user.id, // Your app's user ID
},
client_name: 'Your App Name',
products: ['auth', 'transactions'],
country_codes: ['US'],
language: 'en',
};
try {
const response = await client.linkTokenCreate(request);
res.json({ link_token: response.data.link_token });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Backend: Exchange Public Token
Store access token securely in your database:
app.post('/api/plaid/exchange-token', async (req, res) => {
const { public_token } = req.body;
try {
const response = await client.itemPublicTokenExchange({
public_token: public_token,
});
const access_token = response.data.access_token;
const item_id = response.data.item_id;
// Store access_token securely in your database
await db.users.update(req.user.id, {
plaid_access_token: access_token,
plaid_item_id: item_id,
});
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Retrieving Data
Get Auth (Account/Routing Numbers)
async function getAuthData(access_token) {
const response = await client.authGet({
access_token: access_token,
});
const accounts = response.data.accounts;
const numbers = response.data.numbers;
// ACH numbers
const ach = numbers.ach[0];
console.log('Account:', ach.account);
console.log('Routing:', ach.routing);
return { accounts, numbers };
}
Get Transactions
async function getTransactions(access_token) {
const request = {
access_token: access_token,
start_date: '2024-01-01',
end_date: '2024-12-31',
};
const response = await client.transactionsGet(request);
let transactions = response.data.transactions;
// Handle pagination
while (transactions.length < response.data.total_transactions) {
const paginatedRequest = {
...request,
offset: transactions.length,
};
const paginatedResponse = await client.transactionsGet(paginatedRequest);
transactions = transactions.concat(paginatedResponse.data.transactions);
}
return transactions;
}
Transaction object structure:
{
transaction_id: 'abc123',
account_id: 'xyz789',
amount: 12.34, // Positive = money out, Negative = money in
date: '2024-11-16',
name: 'Starbucks',
merchant_name: 'Starbucks',
category: ['Food and Drink', 'Restaurants', 'Coffee Shop'],
pending: false,
payment_channel: 'in store'
}
Get Balance
async function getBalance(access_token) {
const response = await client.accountsBalanceGet({
access_token: access_token,
});
const accounts = response.data.accounts;
accounts.forEach(account => {
console.log(`${account.name}: ${account.balances.current}`);
});
return accounts;
}
Get Identity
async function getIdentity(access_token) {
const response = await client.identityGet({
access_token: access_token,
});
const identity = response.data.accounts[0].owners[0];
console.log('Name:', identity.names[0]);
console.log('Email:', identity.emails[0].data);
console.log('Phone:', identity.phone_numbers[0].data);
return response.data;
}
Webhooks
Setup Webhook URL
Configure in Plaid Dashboard or via API when creating link token:
{
webhook: 'https://your-domain.com/api/plaid/webhook',
}
Webhook Verification
Verify webhook authenticity:
const crypto = require('crypto');
app.post('/api/plaid/webhook', express.json(), async (req, res) => {
const { webhook_type, webhook_code } = req.body;
// Verify webhook signature
const plaidSignature = req.headers['plaid-verification'];
const timestamp = req.headers['plaid-timestamp'];
const payload = JSON.stringify(req.body);
const hash = crypto
.createHmac('sha256', process.env.PLAID_WEBHOOK_SECRET)
.update(`${timestamp}.${payload}`)
.digest('hex');
if (hash !== plaidSignature) {
return res.status(401).send('Invalid signature');
}
// Handle webhook events
if (webhook_type === 'TRANSACTIONS') {
switch (webhook_code) {
case 'INITIAL_UPDATE':
console.log('Initial transactions available');
break;
case 'DEFAULT_UPDATE':
console.log('New transactions available');
// Fetch new transactions
break;
case 'TRANSACTIONS_REMOVED':
console.log('Transactions removed');
break;
}
}
res.json({ received: true });
});
Common webhook events:
TRANSACTIONS: INITIAL_UPDATE- First batch readyTRANSACTIONS: DEFAULT_UPDATE- New transactions availableITEM: ERROR- Connection issueITEM: PENDING_EXPIRATION- Credentials expiring soonAUTH: AUTOMATICALLY_VERIFIED- Micro-deposit verification complete
Environments
Sandbox (testing):
basePath: plaid.PlaidEnvironments.sandbox
- Test credentials:
user_good/pass_good - No real bank connections
- Simulate transactions
Development (limited live connections):
basePath: plaid.PlaidEnvironments.development
- Up to 100 live bank connections
- Free for testing
Production (live):
basePath: plaid.PlaidEnvironments.production
- Real bank connections
- Requires approval and billing
Error Handling
Update Mode (Re-authentication)
When credentials expire or change:
const { open } = usePlaidLink({
token: linkToken,
onSuccess: async (public_token, metadata) => {
// Item re-linked successfully
},
});
// Create link token with update mode
const request = {
access_token: existing_access_token,
// ... other config
};
Common Errors
ITEM_LOGIN_REQUIRED:
- User needs to re-authenticate
- Solution: Trigger Link in update mode
RATE_LIMIT_EXCEEDED:
- Too many requests
- Solution: Implement exponential backoff
PRODUCT_NOT_READY:
- Data still syncing
- Solution: Wait for webhook or retry
Security Best Practices
-
Never expose secret keys client-side:
- Use
PLAID_CLIENT_IDandPLAID_SECRETserver-side only - Link tokens are safe for client use
- Use
-
Encrypt access tokens:
- Store access tokens encrypted in database
- Never log access tokens
-
Verify webhook signatures:
- Prevents forged webhooks
- Use crypto verification
-
Use HTTPS:
- Required for webhook endpoints
- Required for production
-
Implement proper user consent:
- Clear disclosure about data access
- Follow Plaid's branding guidelines
Next.js API Route Example
Create link token:
// 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',
});
return NextResponse.json({ link_token: response.data.link_token });
}
Testing in Sandbox
Test credentials:
- Username:
user_good - Password:
pass_good - MFA:
1234
Test institutions:
- "Platypus Bank" - Full feature support
- "Tartan Bank" - OAuth flow
- "Houndstooth Bank" - Error testing
Useful Resources
- Official Docs: https://plaid.com/docs/
- API Reference: https://plaid.com/docs/api/
- Plaid Link: https://plaid.com/docs/link/
- Webhooks: https://plaid.com/docs/api/webhooks/
- Quickstart: https://github.com/plaid/quickstart
Implementation Checklist
- Sign up for Plaid account
- Get client ID and secret keys
- Install Plaid SDK:
npm install plaid react-plaid-link - Set environment variables
- Implement link token creation endpoint
- Implement token exchange endpoint
- Integrate Plaid Link on frontend
- Store access tokens securely (encrypted)
- Set up webhook endpoint with verification
- Handle ITEM_LOGIN_REQUIRED errors
- Test with sandbox credentials
- Request production access
- Implement error handling and retry logic
- Add proper user consent/disclosure
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?