# Rate limit errors (HTTP 429) handled gracefully

- **Pattern:** `ab-001293` (`error-resilience.graceful-degradation-shutdown.rate-limit-handling`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001293
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001293)

## Why it matters

HTTP 429 (Too Many Requests) responses from third-party APIs — OpenAI, Stripe, SendGrid, Twilio — are not errors; they are instructions. An application that treats them as generic failures and retries immediately makes the rate-limit situation worse and may get its API key suspended. CWE-703 (improper check for exceptional conditions) applies. Users who trigger rate-limited operations see cryptic failures when they should see a clear message like 'You\'ve sent too many requests. Please wait 60 seconds.' The `Retry-After` header provides the exact wait time — ignoring it is wasteful and user-hostile.

## Severity rationale

Low severity for individual users, but repeated unhandled 429s can trigger API key suspension, causing a service-wide outage for all users simultaneously.

## Remediation

Detect 429 responses explicitly, extract the `Retry-After` header, and surface a user-readable wait message. Do not auto-retry 429s without respecting the header delay.

```ts
// lib/api-client.ts
export async function apiFetch(url: string, options?: RequestInit) {
  const res = await fetch(url, options)
  if (res.status === 429) {
    const retryAfter = parseInt(res.headers.get('Retry-After') ?? '60', 10)
    throw new RateLimitError(
      `Too many requests. Please wait ${retryAfter} seconds before trying again.`,
      retryAfter
    )
  }
  return res
}
```

In the UI, catch `RateLimitError` and show a countdown or disabled button with the remaining wait time rather than a generic error toast.

## Detection

- **ID:** `rate-limit-handling`
- **Severity:** `low`
- **What to look for:** Count all external API integrations. Enumerate which handle 429 (rate limit) responses with backoff vs. which crash or retry immediately. Check whether the application handles HTTP 429 (Too Many Requests) responses. Look for Retry-After header extraction and display of user-friendly messages.
- **Pass criteria:** Rate limit errors (429) are caught and displayed to users with a friendly message; Retry-After header is extracted and used to show when they can retry. At least 80% of external API integrations must handle 429 responses with appropriate backoff.
- **Fail criteria:** 429 responses are not handled; no Retry-After header extraction; user sees raw error.
- **Skip (N/A) when:** The application makes no external API requests or is not rate-limited.

- **Cross-reference:** For retry logic, see `retry-logic-backoff`. For circuit breaker, see `circuit-breaker-pattern`.
- **Detail on fail:** `"Rate limit responses not handled. User sees 'Too Many Requests' error"` or `"429 responses not distinguished from other errors"`
- **Remediation:** Handle rate limits:

  ```ts
  // lib/api-client.ts — rate limit handling
  if (res.status === 429) { const retryAfter = parseInt(res.headers.get('Retry-After') || '60'); await sleep(retryAfter * 1000); return retry() }
  ```

  ```ts
  async function fetchWithRateLimitHandling(url: string) {
    try {
      const response = await fetch(url)
      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After')
        const seconds = retryAfter ? parseInt(retryAfter) : 60
        throw new RateLimitError(
          `Too many requests. Please wait ${seconds} seconds before trying again.`,
          seconds
        )
      }
      return response
    } catch (error) {
      if (error instanceof RateLimitError) {
        // Display to user with countdown
        toast.warning(error.message)
      }
      throw error
    }
  }
  ```

## External references

- cwe CWE-703
- iso-25010 reliability.fault-tolerance

Taxons: error-resilience

HTML version: https://auditbuffet.com/patterns/ab-001293
