> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cfosilvia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# PLAID RATE LIMIT HANDLING

# Plaid API Rate Limit Handling

This document outlines the rate limit handling strategy implemented across the Portfolio Tracker application for Plaid API integration.

## Rate Limit Context

Plaid enforces two types of rate limits that we need to accommodate:

1. **Per-Item Rate Limit**: 5 requests per minute per item
2. **Global Client Rate Limit**: 1,200 requests per minute across all items

## Implementation Strategy

Our implementation uses a hybrid approach of proactive prevention and reactive handling to manage rate limits effectively.

### 1. Routes with Rate Limit Protection

Rate limit handling has been implemented in the following routes:

* `/app/api/fetch-balances/route.ts` - User-triggered balance updates
* `/app/api/connect-bank/route.ts` - Initial bank connection
* `/app/api/relink/route.ts` - Account re-authentication
* `/app/api/cron/schedule-portfolio-briefs/process-portfolio-brief/route.ts` - Portfolio brief generation
* `/app/api/cron/daily-snapshots/route.ts` - End-of-day snapshots
* `/app/api/plaid/webhook/route.ts` - Webhook processing

### 2. Rate Limit Detection

We detect rate limits through multiple signals:

```typescript theme={null}
const isRateLimitError =
  error.response?.status === 429 ||
  errorCode === 'RATE_LIMIT_EXCEEDED' ||
  errorType === 'RATE_LIMIT_EXCEEDED';
```

### 3. Prevention Strategies

#### 3.1 Global Delay Between Requests

```typescript theme={null}
// Simple global rate limiting
const GLOBAL_REQUEST_DELAY = 250; // ms delay between Plaid API calls

// Implementation
await new Promise(resolve => setTimeout(resolve, GLOBAL_REQUEST_DELAY));
```

This ensures we never exceed:

* 240 requests per minute (60,000ms ÷ 250ms)
* Well below Plaid's 1,200/minute global limit

#### 3.2 Staleness Checks

Before making API calls, we check if the data is already recent enough:

* **User-Triggered Updates**: Uses client-side Map to track last update time with 60-second window
* **Portfolio Brief**: Checks if balance was updated in last 30 minutes
* **Daily Snapshots**: Checks if balance was updated today

### 4. Reactive Strategies

#### 4.1 Fixed Retry with Backoff (Daily Snapshots)

```typescript theme={null}
// Fixed delay for rate limit retries
const RATE_LIMIT_RETRY_DELAY = 25000; // 25 seconds
const MAX_RETRIES = 3;

// When rate limited:
if (isRateLimitError) {
  // Wait 25 seconds before retry
  await new Promise(resolve => setTimeout(resolve, RATE_LIMIT_RETRY_DELAY));
}
```

#### 4.2 Exponential Backoff with Jitter (Portfolio Brief)

```typescript theme={null}
// For retryable errors including rate limits
const baseDelay = RETRY_DELAY * Math.pow(2, attempts - 1); // Exponential growth
const jitter = Math.random() * 0.5 * baseDelay; // Add randomness
const delay = baseDelay + jitter;
```

### 5. ITEM\_LOGIN\_REQUIRED Handling

When we detect an expired item (credentials need updating):

```typescript theme={null}
if (errorCode === 'ITEM_LOGIN_REQUIRED') {
  // Update the item as expired in the database
  await supabaseClient
    .from('items')
    .update({ is_expired: true })
    .eq('item_id', item.item_id);
    
  // Skip further processing for this item
}
```

## Effectiveness

This hybrid approach ensures:

1. **Per-Item Limit**: Handled by appropriate delays between retries
2. **Global Limit**: Managed by small delays between all requests
3. **Expired Items**: Properly marked in database for user notification

These strategies work together to provide robust protection against rate limits while ensuring the application remains responsive and data stays fresh.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.