# API Rate Limits: What They Are, How They Silently Kill Automations, and How to Build Around Them

By Abhiman Sundararajan, Abhiman Labs · Published 2026-05-18

Original: https://abhiman.io/blog/api-rate-limits-explained/

You build an automation. You test it. It works perfectly. You hand it off, everyone is happy, and it runs smoothly for weeks. Then one morning something breaks. Not loudly, not with an obvious error. Just silently. Records stop syncing. Notion pages are not updating. Data is drifting between systems and nobody noticed until a client called.

You dig in. The workflow logs show a bunch of failed requests. The error code is 429.

That is a rate limit. And if you did not design for it upfront, fixing it later is a pain.

## What a rate limit actually is

Every API has one. Stripe, Notion, Attio, HubSpot, Slack, doesn't matter. They all put a cap on how many requests you can send in a given time window.

APIs run on servers. Servers have finite resources. If every developer could hammer them with unlimited requests, the whole thing would fall over for everyone. Rate limits are how API providers protect themselves and make sure no single integration can monopolize the infrastructure.

The limit itself varies by provider. Some count requests per second. Some per minute. Some per day. Some have different limits for read operations versus write operations.

When you exceed the limit, the API stops processing your requests and sends back a 429 response. Too Many Requests. That request failed. Whatever your automation was trying to do, it did not happen.

By default, most automation tools just log the failure and move on. So your data does not sync. Your record does not update. Your trigger does not fire. And unless you built proper error handling, you might not know for hours.

## The actual numbers for the APIs you probably use

You need to know exactly what you are working with before you build against it.

### Notion API: 3 requests per second

Three. Per second. Per integration.

Notion allows some burst above this average, meaning you can briefly spike higher before getting throttled. But the sustained average is 3/s. In practical terms that is about 180 requests per minute, or roughly 10,800 per hour.

That sounds fine until you are syncing a CRM with 500 contacts. Or processing a webhook that needs to create a page, update five properties, and link three relations. Suddenly you are burning through 4 to 6 requests per record and the math stops working.

Notion also has a payload size limit: 1,000 block elements per request and a 500KB total body size. Hit either of those and you get a different kind of error, but it is worth knowing both limits exist.

### Stripe API: 25 to 100 requests per second

Stripe is more generous. The default limit for most endpoints is 25 requests per second. Read operations like listing charges or fetching customer data go up to 100/s. The Search API is capped at 20/s. Payout creation is at 15/s with a maximum of 30 concurrent requests.

So Stripe is less likely to be your bottleneck in normal operation. But if you are building something that processes bulk payment data, like syncing historical invoice records or doing a large reconciliation run, you can hit those limits faster than you expect.

### Attio API: 100 reads, 25 writes per second

Attio is generous on reads. 100 read requests per second gives you a lot of headroom. But writes are capped at 25/s, which is the same as Notion's total limit.

Attio also has something called score-based rate limiting on their more complex query endpoints. When you run a query with lots of filters and sorts on a large dataset, it gets assigned a complexity score. If that score is too high, you get rate limited regardless of how many requests per second you are sending. The window for this is 10 seconds. It is well documented in their API docs, but it trips people up because the 429 they see on a complex list query looks identical to a regular rate limit error.

> **Diagram: API rate limits: requests per second**
>
> Notion
>
> 0 / 3
>
> Stripe
>
> 25 / s
>
> Attio
>
> 25 writes / s

## Why it hits harder than you think

The math always looks fine in isolation. 3 requests per second is 180 per minute. Your integration does not send 180 requests per minute, so on paper you have room. Until you do not.

The problem is spikes. Real automations are not evenly distributed. They cluster.

Someone adds 50 new contacts to a CRM in one go. A webhook fires for all 50 simultaneously. Each contact needs a Notion page created, properties set, and relations linked. That is 150 to 200 Notion API requests arriving in the same second. You have a limit of 3.

Or a client clicks a button that triggers a workflow which queries three databases, creates two pages, updates four relations, and sends a Slack message. That single click just fired 10 API calls in less than a second.

The automation worked fine in testing because you were only testing one contact at a time, one click at a time. Production does not work that way.

## The burst concept

Most APIs allow short bursts above the stated limit. Notion says their 3/s limit is an average. Stripe uses something called a token bucket algorithm where you accumulate allowance over time and can spend it in bursts.

What this means in practice is that you will not get rate limited the exact moment you send your 4th request in a second. The system tolerates brief spikes. But sustained traffic above the limit will get throttled.

Do not design your system to rely on burst tolerance. It is a buffer, not a feature. Treat the stated limit as the real limit and build for it.

## How to actually handle rate limits

There are three things that work. You probably need all three for anything serious.

### 1. Exponential backoff with jitter

When you get a 429, wait and retry. But not with a fixed wait time. Fixed retries cause what is called a thundering herd problem. All your requests waited the same amount of time, so they all retry simultaneously, and you hit the rate limit again immediately.

Exponential backoff means you double the wait time on each retry. Jitter means you add a small random amount on top so all your retries don't land at exactly the same moment.

> **Diagram: Exponential backoff with jitter: retry timeline**
>
> ✗ fail
>
> ✗ retry
>
> 1s + jitter
>
> ✗ retry
>
> 2s + jitter
>
> ✗ retry
>
> 4s + jitter
>
> ✓ success
>
> 8s + jitter

Here is the basic pattern:

*javascript*

```
async function requestWithBackoff(fn, maxRetries = 5) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (error.status === 429 && attempt < maxRetries - 1) {
        // Check if the API gave us a Retry-After header
        const retryAfter = error.headers?.['retry-after'];
        const waitTime = retryAfter
          ? parseInt(retryAfter) * 1000
          : Math.pow(2, attempt) * 1000 + Math.random() * 1000;

        console.log(`Rate limited. Waiting ${waitTime}ms before retry ${attempt + 1}`);
        await new Promise(resolve => setTimeout(resolve, waitTime));
      } else {
        throw error;
      }
    }
  }
}
```

Always check for the Retry-After header first. Notion sends one. Attio sends one. Stripe sends one. It tells you exactly how long to wait. Using it is more reliable than calculating your own backoff.

### 2. Queues

For bulk operations, the right answer is a queue. Instead of firing all 50 requests simultaneously, you put them all in a queue and process them at a controlled rate.

In n8n you can do this with the batch node or by adding a Wait node between iterations. Not elegant but it works. For proper queue systems you would use something like BullMQ or even just a simple setTimeout-based throttle.

You know your limit is 3 requests per second. So you process one request every 350ms and you never get close to the limit. Slower but bulletproof.

> **Diagram: Queue throttle: controlled request rate**
>
> 50 events
>
> incoming burst
>
> Queue
>
> backlog
>
> Worker
>
> 1 per 350ms
>
> API
>
> never 429

### 3. Batching

Some APIs let you combine multiple operations into a single request. Notion does not have a batch API for creating pages, but it does let you set multiple properties in a single update request. Attio has batch endpoints for certain operations.

Every time you can replace 5 requests with 1 request, you buy yourself room to breathe under the rate limit. Always look for batching options before assuming you need to make individual calls for everything.

## The silent failure problem

What makes rate limits dangerous is that the failure is silent.

A 429 is not a crash. Your automation keeps running. It just skips the request that got rate limited. If you are not explicitly handling 429 responses and either retrying or logging them somewhere visible, you will not know the data did not sync. The automation looks healthy in the dashboard. The error is in the data.

I have seen this in production more than once. A sync that worked for weeks started silently dropping records during a busy period when more data was flowing through. Nobody noticed until the Notion database was visibly inconsistent with the source system.

The fix for this is boring but important. Log every 429 response. Alert on them if the frequency gets above a threshold. Store failed requests somewhere so they can be retried rather than dropped.

## What this looks like in practice

When I build CRM to Notion syncs, rate limits are not an afterthought. They are part of the design from day one.

For anything that needs to sync bulk records, the flow uses a queue. For real-time webhook-triggered updates, exponential backoff handles the occasional spike. And every failed request gets logged with enough context to know exactly which record failed and why, so nothing gets silently lost.

If your automation is running at low volume and you have never hit a 429, that is great. But it is worth building the retry logic before you need it rather than after a client notices records are missing.

I build production-grade n8n automations and Notion systems at Abhiman Labs. If rate limits and reliability are something you want handled properly rather than retrofitted, the [work page](https://abhiman.io/work/) is a good place to start.

## Frequently asked questions

### What is an API rate limit?

A rate limit is a cap on how many requests you can send to an API in a given time window. APIs run on servers with finite resources, so providers use rate limits to prevent any single integration from overwhelming their infrastructure. When you exceed the limit, the API returns a 429 Too Many Requests error.

### What does a 429 error mean in automation?

A 429 error means you have exceeded the API's rate limit. The dangerous thing about 429 errors in automation is that they are often silent. Most tools log the failure and move on, so your data simply does not sync without any obvious crash or alert. You need explicit handling to log, retry, or queue failed requests.

### What are the rate limits for Notion, Stripe, and Attio?

Notion allows 3 requests per second per integration, with a 1,000 block and 500KB payload limit per request. Stripe allows 25 requests per second for most endpoints and up to 100/s for read operations. Attio allows 100 read requests and 25 write requests per second, with additional score-based limits on complex queries.

### What is exponential backoff with jitter?

Exponential backoff means doubling the wait time on each retry after a rate limit error: 1s, 2s, 4s, 8s, and so on. Jitter adds a small random amount on top so that multiple retrying requests do not all land at exactly the same moment, which would recreate the rate limit problem. Always check for a Retry-After header first. Most APIs send one that tells you exactly how long to wait.

### How do queues help with API rate limits?

Instead of firing all requests simultaneously, a queue stores them and processes them at a controlled rate. If your limit is 3 requests per second, processing one request every 350ms keeps you safely under the limit regardless of how many arrive at once. In n8n you can use a Wait node between iterations; for production systems use BullMQ or a similar queue library.

### Why do rate limit failures happen silently?

A 429 error is not a crash. Your automation keeps running. It just skips the request that got rate limited. Unless you explicitly handle 429 responses with retries or visible logging, the data simply does not sync. The automation dashboard looks healthy. The error is only visible in the data itself, often discovered only when a client notices records are missing or inconsistent.

## Related reading

- [Automation Drift: Why the Workflow You Built 6 Months Ago Is Quietly Lying to You](https://abhiman.io/blog/automation-drift/)
- [Webhooks Explained the Way Nobody Explains Them](https://abhiman.io/blog/webhooks-explained/)
