Tips > Data, APIs & Webhooks

Implement Exponential Backoff on Rate-Limited APIs

When an API returns a `429 Too Many Requests` error, retrying immediately wastes attempts.

TipIntermediate3 min read

When an API returns a 429 Too Many Requests error, retrying immediately wastes attempts. Configure n8n's retry settings with exponential backoff so each retry waits longer than the last. This dramatically improves success rates on rate-limited APIs and avoids burning your retry budget.

Real-world example: A workflow calls the Twitter API, which has strict rate limits. Without backoff, all 3 retries fire within 3 seconds and all fail. With exponential backoff, the third retry succeeds after the rate limit window resets.


# HTTP Request node retry settings (node settings panel)

Settings:
  Retry On Fail: true
  Max Retries: 5
  Wait Between Retries: 2000    # 2 seconds base wait

  # n8n does not have built-in exponential backoff,

  # so implement it in a Code node wrapper (see below)

```text
Implement true exponential backoff with a Code node wrapper:

```javascript
// Code node: "API Call with Exponential Backoff"
const maxRetries = 5;
const baseDelay = 1000; // 1 second

async function callWithBackoff(url, options) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await this.helpers.httpRequest({
        method: 'GET',
        url: url,
        ...options
      });
      return { success: true, data: response, attempts: attempt + 1 };
    } catch (error) {
      const statusCode = error.statusCode || error.response?.status;

      if (statusCode === 429 && attempt < maxRetries) {
        // Exponential backoff: 1s, 2s, 4s, 8s, 16s
        const delay = baseDelay * Math.pow(2, attempt);

        // Check for Retry-After header
        const retryAfter = error.response?.headers?.['retry-after'];
        const waitTime = retryAfter
          ? parseInt(retryAfter) * 1000
          : delay;

        await new Promise(resolve => setTimeout(resolve, waitTime));
        continue;
      }

      // Non-retryable error or max retries exceeded
      return {
        success: false,
        error: error.message,
        statusCode,
        attempts: attempt + 1
      };
    }
  }
}

const result = await callWithBackoff.call(
  this,
  'https://api.twitter.com/2/tweets/search/recent',
  {
    headers: {
      'Authorization': `Bearer ${$json.bearerToken}`
    },
    qs: { query: $json.searchQuery }
  }
);

return [{ json: result }];
```text
```yaml
Retry timeline comparison:

Without backoff (fixed 1s):
  Attempt 1: t=0s    --> 429
  Attempt 2: t=1s    --> 429
  Attempt 3: t=2s    --> 429
  Result: FAILED (rate limit window is 15s)

With exponential backoff:
  Attempt 1: t=0s    --> 429
  Attempt 2: t=1s    --> 429
  Attempt 3: t=3s    --> 429
  Attempt 4: t=7s    --> 429
  Attempt 5: t=15s   --> 200 OK
  Result: SUCCESS (waited long enough for rate limit reset)
```text
> **Tip: Respect the Retry-After header**
>
> Many APIs include a `Retry-After` header in their 429 responses. This tells you exactly how long to wait. Always check for it before falling back to calculated backoff times.

This approach maximizes your chances of success without wasting retries or getting your API key throttled further.

**Related:** [Use Structured Output (JSON Mode) for Parseable Responses](../ai-and-llm-integration/01-use-structured-output-json-mode-for-parseable-responses.md) | [Configure Payload Size and Binary Data Mode for Large Files](../performance-and-large-files/01-configure-payload-size-and-binary-data-mode-for-large-files.md)

Want this running in your stack?

I build production n8n and Cloudflare automation for teams — the same engineering behind HarperFlow. Fixed-price, escrow-protected, US-based.