Handle HTTP 429 Too Many Requests in Axios

Handling an HTTP 429 Too Many Requests error in Axios is essential for building resilient applications that interact with rate-limited APIs. This guide outlines how to detect 429 errors, respect standard rate-limiting headers like Retry-After, and automatically retry failed requests using both custom Axios interceptors and the popular axios-retry library.


Understanding the 429 Status Code

An HTTP 429 status code indicates that the client has sent too many requests in a given amount of time. Most well-designed APIs include a Retry-After response header indicating how many seconds (or an explicit HTTP date) the client should wait before making another request.


The most straightforward way to handle 429 responses is using the axios-retry package. It provides built-in exponential backoff and custom retry conditions.

1. Install the package

npm install axios axios-retry

2. Configure Axios with Automatic Retries

const axios = require('axios');
const axiosRetry = require('axios-retry');

const client = axios.create({ baseURL: 'https://api.example.com' });

axiosRetry(client, {
  retries: 3, // Number of retry attempts
  retryDelay: (retryCount, error) => {
    // Check if the server sent a Retry-After header
    const retryAfter = error.response?.headers['retry-after'];
    if (retryAfter) {
      return parseInt(retryAfter, 10) * 1000;
    }
    // Fall back to exponential backoff
    return axiosRetry.exponentialDelay(retryCount);
  },
  retryCondition: (error) => {
    // Retry only if status code is 429
    return error.response?.status === 429;
  },
});

async function fetchData() {
  try {
    const response = await client.get('/data');
    console.log(response.data);
  } catch (error) {
    console.error('Request failed after retries:', error.message);
  }
}

fetchData();

Method 2: Custom Axios Response Interceptor

If you prefer not to install external dependencies, you can implement retry logic using Axios response interceptors.

const axios = require('axios');

const client = axios.create();

// Helper to delay execution
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

client.interceptors.response.use(
  (response) => response,
  async (error) => {
    const { config, response } = error;

    // Check if error is 429 and retry count is not exceeded
    if (response && response.status === 429) {
      config.__retryCount = config.__retryCount || 0;
      const maxRetries = 3;

      if (config.__retryCount < maxRetries) {
        config.__retryCount += 1;

        // Parse Retry-After header or calculate exponential backoff
        const retryAfterHeader = response.headers['retry-after'];
        let delay = Math.pow(2, config.__retryCount) * 1000; // Default exponential backoff

        if (retryAfterHeader) {
          delay = !isNaN(retryAfterHeader)
            ? parseInt(retryAfterHeader, 10) * 1000
            : Math.max(0, new Date(retryAfterHeader).getTime() - Date.now());
        }

        console.warn(`429 received. Retrying attempt #${config.__retryCount} in ${delay}ms...`);
        await sleep(delay);

        // Re-execute original request
        return client(config);
      }
    }

    return Promise.reject(error);
  }
);

Best Practices for Handling 429 Responses