> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developers.beehiiv.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developers.beehiiv.com/_mcp/server.

The beehiiv API has a rate limit of **180 requests per minute** on a per-organization basis. This is to prevent abuse and ensure the stability of the API.

If you are making requests to the beehiiv API at a rate that exceeds the rate limit, you will receive a `429` (Too Many Requests) error.

To prevent this, we recommend implementing rate limiting and methods like exponential backoff to retry requests that fail due to rate limiting.

## Headers

Each response from the beehiiv API will include the following headers to assist you in your rate limiting implementation:

* `RateLimit-Limit`: The maximum number of requests that are allowed in the current period.
* `RateLimit-Remaining`: The number of requests remaining in the current period.
* `RateLimit-Reset`: The time (in [seconds since the Unix epoch](https://en.wikipedia.org/wiki/Unix_time)) at which the current period will reset.

## Implementation

To effectively implement rate limiting, we recommend instituting a queue system and leveraging [exponential backoff](https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/retry-backoff.html).

Many programming languages and frameworks offer built-in support for queue systems. Some common examples include:

* [Amazon SQS](https://aws.amazon.com/sqs/) (All languages)
* [Upstash QStash](https://upstash.com/docs/qstash/overall/getstarted) (All languages)
* [Sidekiq](https://sidekiq.org/) (Ruby)
* [Goroutines](https://go.dev/tour/concurrency/1) (Go)
* [Laravel Queues](https://laravel.com/docs/12.x/queues) (PHP)
* [Trigger.dev](https://trigger.dev/) (JavaScript)
* [Celery](https://docs.celeryq.dev/) (Python)

Many no-code platforms such as [Zapier](https://zapier.com/apps/beehiiv/integrations) and [Make](https://www.make.com/en/integrations/beehiiv) automatically adhere to our rate limit.

## Example Implementation (JavaScript)

This is a basic example of how to implement rate limiting in your JavaScript code. More complex implementations can be implemented using one of the queue systems mentioned above or by using a library like [Bottleneck](https://github.com/SGrondin/bottleneck).

```javascript
const MAX_REQUESTS_PER_MINUTE = 180;
const MAX_CONCURRENT = 5;
// Beehiiv API: 180 requests / 60 seconds = 3 requests per second.
// 1000ms / 3 requests = ~333ms per request.
const MIN_TIME_BETWEEN_REQUESTS_MS = 350;

let requestQueue = [];
let activeRequestsCount = 0;
let requestTimestamps = [];

async function makeApiCall(endpoint, params) {
  console.log(`Making API call to: ${endpoint} with params:`, params);
  // Replace with your actual fetch/axios call
  // Ensure your actual API call function is asynchronous (returns a Promise)
  return fetch(`https://api.beehiiv.com/v2/${endpoint}`, {
    method: 'GET', // or 'POST', etc.
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY', // Replace YOUR_API_KEY
      'Content-Type': 'application/json'
    },
    // body: JSON.stringify(params) // if it's a POST/PUT request
  })
  .then(response => {
    if (!response.ok) {
      if (response.status === 429) {
        console.warn('Rate limit hit (429). The custom rate limiter should ideally prevent this. Check logic or external factors.');
      }
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  });
}

function processRequestQueue() {
  if (requestQueue.length === 0) {
    return;
  }

  const now = Date.now();

  // 1. Prune old timestamps (older than 1 minute)
  requestTimestamps = requestTimestamps.filter(timestamp => now - timestamp < 60000);

  // 2. Check constraints
  if (activeRequestsCount >= MAX_CONCURRENT) {
    return;
  }

  if (requestTimestamps.length >= MAX_REQUESTS_PER_MINUTE) {
    console.log('Rate limit per minute reached. Waiting for window to reset...');
    const timeToWait = (requestTimestamps[0] + 60000) - now + 100;
    setTimeout(processRequestQueue, Math.max(0, timeToWait));
    return;
  }
  
  if (requestTimestamps.length > 0) {
      const lastDispatchedTime = requestTimestamps[requestTimestamps.length - 1];
      const timeSinceLastDispatched = now - lastDispatchedTime;
      if (timeSinceLastDispatched < MIN_TIME_BETWEEN_REQUESTS_MS) {
          const delay = MIN_TIME_BETWEEN_REQUESTS_MS - timeSinceLastDispatched;
          console.log(`Min time between requests. Waiting ${delay}ms...`);
          setTimeout(processRequestQueue, Math.max(0,delay));
          return;
      }
  }

  // 3. Dequeue and process the request
  const { fnToCall, resolve, reject, args } = requestQueue.shift();
  
  activeRequestsCount++;
  requestTimestamps.push(Date.now()); 

  fnToCall(...args)
    .then(resolve)
    .catch(reject)
    .finally(() => {
      activeRequestsCount--;
      setTimeout(processRequestQueue, 0); 
    });
}

function throttledApiCall(endpoint, params) {
  return new Promise((resolve, reject) => {
    requestQueue.push({ fnToCall: makeApiCall, resolve, reject, args: [endpoint, params] });
    setTimeout(processRequestQueue, 0); 
  });
}

// --- Example Usage ---
async function fetchAllPosts() {
  try {
    const postIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10 /* ... more ids ... */];
    console.log(`Attempting to fetch ${postIds.length} posts sequentially managed by rate limiter...`);
    
    const promises = postIds.map(id => {
      return throttledApiCall(`posts/${id}`)
        .then(post => {
          console.log(`Successfully fetched post ${id}:`, post.id);
          return post;
        })
        .catch(error => {
          console.error(`Failed to fetch post ${id}:`, error.message);
          throw error; 
        });
    });

    const results = await Promise.all(promises);
    console.log('All post fetch attempts completed.');
    console.log('Fetched posts data:', results);
    return results;
  } catch (error) {
    console.error('Error in fetchAllPosts orchestration:', error.message);
  }
}

// To use this:
// fetchAllPosts().then(() => console.log("fetchAllPosts example finished."));

// For debugging/monitoring, you can add more console logs within processRequestQueue or around the `activeRequestsCount` and `requestTimestamps` manipulations.
```

### How it Works:

* **Configuration**:
  * `MAX_REQUESTS_PER_MINUTE`: Set to 180, matching the beehiiv API limit.
  * `MAX_CONCURRENT`: Limits how many requests can be active simultaneously (e.g., 5). This prevents overwhelming the network or the server with too many connections at once, even if within the overall rate limit.
  * `MIN_TIME_BETWEEN_REQUESTS_MS`: Ensures a minimum delay between the start of each request (e.g., 350ms). This helps distribute requests more evenly and provides an additional safeguard against hitting the rate limit due to bursts.

* **State Management**:
  * `requestQueue`: An array that holds API calls waiting to be made. Each item in the queue is an object containing the function to call (`fnToCall`), its arguments (`args`), and the `resolve` and `reject` functions of the Promise returned by `throttledApiCall`.
  * `activeRequestsCount`: Tracks the number of currently in-flight API requests.
  * `requestTimestamps`: Stores the timestamps of when each request was dispatched. This array is used to ensure that no more than `MAX_REQUESTS_PER_MINUTE` are made within any rolling 60-second window.

* **`makeApiCall(endpoint, params)`**:
  * This is your actual function that performs the `fetch` request to the beehiiv API. You'll need to replace `'Bearer YOUR_API_KEY'` with your API key and customize the request as needed.

* **`throttledApiCall(endpoint, params)`**:
  * This function acts as a wrapper around your `makeApiCall`. When you want to make an API request in a rate-limited fashion, you call `throttledApiCall` instead of `makeApiCall` directly.
  * It adds your request details to the `requestQueue` and then triggers `processRequestQueue` (asynchronously via `setTimeout`) to attempt to process it.
  * It returns a Promise that will resolve or reject based on the outcome of the actual API call once it's processed.

* **`processRequestQueue()`**:
  * This is the core of the rate limiter. It's called to attempt to process the next request in the queue.
  * It first checks several conditions:
    1. If the `requestQueue` is empty, it does nothing.
    2. It prunes `requestTimestamps` to only keep those within the last 60 seconds.
    3. If `activeRequestsCount` is already at `MAX_CONCURRENT`, it returns, waiting for an active request to complete.
    4. If `requestTimestamps` indicates that `MAX_REQUESTS_PER_MINUTE` have been made in the last 60 seconds, it calculates the time needed to wait until the oldest request in the window expires and schedules `processRequestQueue` to run after that delay.
    5. It checks if the time since the last dispatched request is less than `MIN_TIME_BETWEEN_REQUESTS_MS`. If so, it schedules `processRequestQueue` to run after the necessary delay.
  * If none of the limiting conditions are met, it dequeues a request from `requestQueue`, increments `activeRequestsCount`, records the dispatch timestamp, and executes the API call (`fnToCall`).
  * When the API call's Promise settles (either resolves or rejects), the `finally` block decrements `activeRequestsCount` and calls `setTimeout(processRequestQueue, 0)` to ensure the queue processing continues for any subsequent requests.

* **Example Usage (`fetchAllPosts`)**:
  * This asynchronous function demonstrates how you might schedule multiple API calls using `throttledApiCall`. The rate limiter manages the queue and dispatches these calls according to the defined limits, preventing `429` errors.

This vanilla JavaScript approach helps prevent `429` errors by managing request flow. Remember to adjust `YOUR_API_KEY` and the API endpoints in the `makeApiCall` function.

This vanilla JavaScript example focuses on managing request rates within a single Node.js process, like running a script locally on your machine.

It does not cover persistent storage of rate limit states (which would be needed if the application restarts) or distributed rate limiting across multiple instances. For those scenarios, or for handling very large queues robustly, you would typically integrate server-side queuing systems (like Amazon SQS or others mentioned above) often backed by stores like [Redis](https://redis.io/) or [Valkey](https://valkey.io/).