Error Medic

How to Fix Notion API Rate Limit (HTTP 429) and 502 Bad Gateway Errors

Master Notion API integrations by resolving HTTP 429 rate limit and 502 Bad Gateway errors. Implement exponential backoff, queues, and optimize database queries

Last updated:
Last verified:
1,618 words
Key Takeaways
  • Notion enforces a strict rate limit of 3 requests per second per integration, returning HTTP 429 when exceeded.
  • HTTP 502 Bad Gateway errors typically occur during highly complex database queries or bulk block operations that time out upstream.
  • The most critical fix for 429s is respecting the Retry-After HTTP header and implementing exponential backoff with jitter.
  • For production data pipelines, offload API requests to a message queue (e.g., Redis/Celery) to strictly control concurrency.
Rate Limit Mitigation Strategies Compared
MethodWhen to UseTime to ImplementReliability
Static Sleep/DelaySimple scripts, small one-off data importsLowPoor (Brittle to spikes)
Retry-After + BackoffStandard webhooks, automated sync jobsMediumHigh
Message QueueHigh-volume, enterprise data pipelinesHighMaximum
Query OptimizationWhen encountering frequent 502 Bad Gateway errorsMediumHigh (Reduces load)

Understanding Notion API Rate Limits and 502 Errors

When building robust integrations with the Notion API, developers inevitably encounter two major roadblocks: HTTP 429 Too Many Requests (Rate Limits) and HTTP 502 Bad Gateway errors. Because Notion is a highly flexible, block-based database system, operations that seem simple on the surface can translate to heavy computational load on Notion's backend. To maintain platform stability, Notion enforces aggressive rate limiting and strict timeout thresholds.

The Anatomy of a Notion HTTP 429 Error

The Notion API currently enforces a rate limit of 3 requests per second per integration. This is a hard limit, and burst traffic is not generously tolerated. When your application exceeds this threshold, Notion rejects the request and returns an HTTP 429 status code.

The JSON response body typically looks like this:

{
  "object": "error",
  "status": 429,
  "code": "rate_limited",
  "message": "Rate limit exceeded."
}

Crucially, alongside this response body, the Notion API provides a Retry-After HTTP header. This header contains an integer representing the number of seconds your application must wait before making another request. Ignoring this header and continuing to hammer the API can result in longer temporary bans or integration suspensions.

Why Does the Notion API Return 502 Bad Gateway?

While a 429 error indicates you are sending requests too quickly, an HTTP 502 Bad Gateway error implies that the Notion API infrastructure timed out while trying to process your request.

In the context of Notion, 502s most frequently occur in the following scenarios:

  1. Unoptimized Database Queries: Querying a massive database without utilizing the filter parameter, forcing Notion to scan tens of thousands of pages.
  2. Deep Rollups and Formulas: Querying databases where properties rely on complex, multi-level rollups and formulas that must be calculated on the fly.
  3. Large Page Payloads: Attempting to append hundreds of deeply nested blocks to a page in a single API call.
  4. Upstream Infrastructure Spikes: Temporary degradation of Notion's AWS infrastructure or internal microservices.

Because 502 errors can occur mid-transaction, handling them requires not just retries, but idempotent design to ensure data isn't duplicated.


Step 1: Diagnosing Your Traffic Patterns

Before writing any code, audit your integration's traffic patterns. Are you hitting limits because of a steady stream of requests, or due to sudden spikes (e.g., a webhook triggering a cascade of updates)?

Examine your application logs. If you see bursts of 429s, your application is likely executing parallel requests. Because Notion's limit is 3 requests per second, executing a Promise.all() over an array of 10 Notion pages will immediately trigger a 429.

If you are seeing 502 errors, isolate the specific API endpoints. Are they POST /v1/databases/{id}/query calls? If so, you need to look at pagination and filtering.


Step 2: Implementing the Retry-After Header

The most compliant way to handle Notion API rate limits is to dynamically pause execution based on the Retry-After header.

When you receive a 429, extract the header value. In Node.js, this might look like response.headers.get('retry-after'). In Python, response.headers.get('Retry-After'). Suspend the current thread or async worker for exactly that many seconds before retrying the exact same request.

Note: The Retry-After header is in seconds, but most sleep functions (like JavaScript's setTimeout) expect milliseconds. Always multiply the header value by 1000 in JS.


Step 3: Exponential Backoff and Jitter

Relying solely on the Retry-After header isn't always enough, especially for 502 Bad Gateway errors (which do not return a Retry-After header) or network-level timeouts. You must implement Exponential Backoff with Jitter.

Exponential backoff means that with each subsequent failure, the wait time increases exponentially (e.g., wait 1s, then 2s, then 4s, then 8s).

Jitter adds a random element of time to the backoff period. If you have 5 concurrent workers that all hit a 429 simultaneously, and they all wait exactly 2 seconds before retrying, they will all hit the API at the exact same millisecond again, triggering another 429. By adding random jitter (e.g., 2 seconds + a random float between 0 and 1), you spread out the retries, smoothing the load.


Step 4: Architecting for Resiliency with Queues

If your integration routinely processes hundreds or thousands of pages (e.g., syncing a CRM into Notion), client-side sleep functions will eventually lead to memory leaks or serverless function timeouts (like AWS Lambda's 15-minute limit or Vercel's 10-second limit).

For enterprise-grade reliability, you must decouple the event generation from the Notion API execution using a Message Queue (such as Redis with BullMQ, Celery, or AWS SQS).

  1. Enqueue: When an update is required, push a job payload to your queue.
  2. Consume: Configure a worker to process the queue.
  3. Rate Limit the Worker: Strictly configure the worker to process a maximum of 3 jobs per second.

This completely eliminates 429 errors by ensuring your outbound request rate never exceeds Notion's limits, regardless of how many webhook events your system receives.


Step 5: Optimizing Queries to Prevent 502s

To eliminate 502 Bad Gateway errors, you must reduce the computational burden on Notion's servers.

1. Use Strict Filtering: Never query an entire database unless absolutely necessary. Always use the filter object to restrict the query to pages modified recently (last_edited_time) or matching specific criteria.

2. Reduce Page Size: The page_size parameter defaults to 100. If you are experiencing 502s, reduce page_size to 50 or 25. This forces Notion to return smaller chunks of data, preventing upstream timeouts.

3. Break Up Block Appends: If you are using PATCH /v1/blocks/{block_id}/children to append a massive document, chunk the blocks array. Do not try to append 100 blocks at once if they contain complex formatting or embeds. Append them 20 at a time in sequential API calls.

Frequently Asked Questions

python
import time
import random
import logging
import requests
from requests.exceptions import RequestException

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def notion_api_request(url, headers, payload=None, method='POST', max_retries=5):
    """
    Executes a Notion API request with robust handling for 429 Rate Limits
    and 5xx Server Errors using Exponential Backoff and Jitter.
    """
    retries = 0
    
    while retries < max_retries:
        try:
            if method.upper() == 'POST':
                response = requests.post(url, headers=headers, json=payload)
            elif method.upper() == 'PATCH':
                response = requests.patch(url, headers=headers, json=payload)
            else:
                response = requests.get(url, headers=headers)
                
            # Success
            if response.status_code in (200, 201):
                return response.json()
                
            # Handle 429 Too Many Requests
            if response.status_code == 429:
                retry_after = int(response.headers.get('Retry-After', 1))
                logger.warning(f"429 Rate Limit Hit. Retry-After header: {retry_after}s")
                # Wait exactly as Notion dictates, plus a tiny bit of jitter
                time.sleep(retry_after + random.uniform(0.1, 0.5))
                retries += 1
                continue
                
            # Handle 502 Bad Gateway and other 5xx errors
            if response.status_code >= 500:
                logger.warning(f"{response.status_code} Server Error from Notion. Retrying...")
                # Exponential backoff: 2^retries + jitter (e.g., 1s, 2s, 4s, 8s)
                sleep_time = (2 ** retries) + random.uniform(0.1, 1.0)
                time.sleep(sleep_time)
                retries += 1
                continue
                
            # If it's a 4xx error (other than 429), it's a bad request, do not retry
            response.raise_for_status()
            
        except RequestException as e:
            logger.error(f"Network error: {e}")
            sleep_time = (2 ** retries) + random.uniform(0.1, 1.0)
            time.sleep(sleep_time)
            retries += 1
            
    raise Exception(f"Notion API request failed after {max_retries} retries.")

# Example Usage
if __name__ == "__main__":
    NOTION_TOKEN = "your_integration_token_here"
    DATABASE_ID = "your_database_id_here"
    
    headers = {
        "Authorization": f"Bearer {NOTION_TOKEN}",
        "Content-Type": "application/json",
        "Notion-Version": "2022-06-28"
    }
    
    # Safe querying with smaller page size to prevent 502s
    payload = {
        "page_size": 50
    }
    
    try:
        data = notion_api_request(
            f"https://api.notion.com/v1/databases/{DATABASE_ID}/query",
            headers=headers,
            payload=payload,
            method='POST'
        )
        print(f"Successfully retrieved {len(data.get('results', []))} pages.")
    except Exception as err:
        print(f"Final Failure: {err}")
S

Sarah Jenkins, SRE

Sarah is a Senior Site Reliability Engineer specializing in API integrations, robust microservices, and distributed systems architecture. She writes extensively on handling platform limits and building resilient pipelines.

Sources

Related Articles in Notion Api

Explore More API Errors Guides