A 429 Too Many Requests error occurs when your script exceeds Notion’s strict API rate limit of 3 requests per second. To execute a successful notion api 429 rate limit retry, you must wrap your API calls in a backoff function that catches the rejection, reads the server’s requested delay, and pauses execution.
Use this asynchronous wrapper with the official @notionhq/client SDK:
JavaScript
const { Client } = require("@notionhq/client");
const notion = new Client({ auth: process.env.NOTION_TOKEN });
async function executeWithRetry(apiCall, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await apiCall(); // Attempt the Notion API request
} catch (error) {
if (error.code === 'rate_limited' && attempt < maxRetries - 1) {
// Fallback to 1 second if the header is missing
const delaySeconds = error.headers?.get('retry-after') || 1;
console.warn(`429 Rate Limited. Pausing for ${delaySeconds}s...`);
await new Promise(resolve => setTimeout(resolve, delaySeconds * 1000));
continue;
}
throw error; // Throw standard errors immediately
}
}
}
This script traps the rate limit error before it crashes your Node.js application, forces the JavaScript thread to sleep for the exact duration Notion demands, and automatically tries pushing the payload again.
Prerequisites
- Node.js Environment: Your automation must be running in a standard Node.js or edge runtime environment capable of resolving JavaScript Promises.
- Official Notion SDK: The code block relies on the error mapping (
error.code === 'rate_limited') provided natively by the@notionhq/clientNPM package. - Sequential Looping: Do not invoke this wrapper inside a
Promise.all()array mapping. You must execute your master loop sequentially usingfor...ofto prevent creating immediate micro-spikes that trigger secondary 429s.
The Syntax Breakdown
This backoff function operates as middleware, sitting between your raw data and the Notion servers to manage network traffic organically.
for (let attempt = 0; attempt < maxRetries; attempt++)— The safety loop. Setting a hard retry cap prevents your script from getting stuck in an infinite loop if the Notion API experiences an extended outage.return await apiCall();— Evaluates the anonymous function you pass into the wrapper. If the network call succeeds, it instantly returns the Notion page object and exits the loop.error.code === 'rate_limited'— The intercept filter. The wrapper ignores standard 400 Validation errors or 404 Not Found errors, allowing them to crash the script as intended. It only activates the retry logic when it specifically detects a rate limit block.error.headers?.get('retry-after')— Interrogates the HTTP response headers. Notion tells you exactly how many seconds you need to wait before sending another request. Capturing this exact integer is infinitely more efficient than guessing an arbitrary delay time.await new Promise(resolve => setTimeout(resolve, delaySeconds * 1000))— The thread pause. This forces the Node.js event loop to halt the current async function execution. BecausesetTimeoutexpects milliseconds, we multiply the header’s second-based value by 1,000.
Real-World Example: Migrating IT Support Tickets
A sysadmin is migrating an archive of 850 legacy IT support tickets from Zendesk into a central Notion database. The script extracts the Zendesk data, transforms it into Notion block objects, and attempts to create the pages.
The admin initially fires the payload using an asynchronous forEach loop. Because Node.js attempts to process all 850 requests almost simultaneously, the script hits Notion’s 3-requests-per-second ceiling instantly. The terminal floods with APIResponseError: Rate limited exceptions, and the script terminates after only creating 14 tickets.
The Script Apply
The sysadmin implements the middleware wrapper and changes the array iteration to a sequential for...of loop.
JavaScript
// Array of 850 ticket payloads
const zendeskTickets = [...];
async function migrateTickets() {
for (const ticket of zendeskTickets) {
// Pass the Notion create method into the retry wrapper
await executeWithRetry(() => notion.pages.create({
parent: { database_id: "db_12345" },
properties: ticket.properties
}));
}
console.log("Migration Complete");
}
migrateTickets();
Output Terminal Log
| Request Batch | Network Status | Script Action |
| Ticket 1 – 3 | 200 OK | Pages Created |
| Ticket 4 | 429 Too Many Requests | Pausing for 1s... |
| Ticket 4 (Retry) | 200 OK | Page Created |
| Ticket 5 – 7 | 200 OK | Pages Created |
By routing the creation calls through the wrapper, the script organically throttles itself. When it hits the ceiling on Ticket 4, it sleeps for one second, successfully retries, and resumes the migration without manual intervention.
Common Related Errors & Fixes
- Unhandled Promise Rejection Timeout -> Cause: You wrapped the retry logic inside an asynchronous mapping function like
array.map(), causing hundreds of threads to sleep and wake up simultaneously, overwhelming your local memory heap. -> Fix: Always use a standardfororfor...ofloop when executing API calls that you suspect will trigger rate limits. - 502 Bad Gateway Errors Persist -> Cause: You set the
maxRetriesvariable too high (e.g., 50). If Notion is experiencing a severe internal routing degradation, continuous polling will eventually result in a gateway timeout from their load balancers. -> Fix: Keep the maximum retries between 3 and 5. If the payload fails 5 consecutive times, throw a fatal error to kill the script and investigate the endpoint health.
Key Takeaways
- The core fix requires wrapping your API execution in an asynchronous
try/catchblock that specifically isolates the rate limit error code. - Always interrogate the
Retry-Afterheader. Using static timers (like blindly pausing for 5 seconds every time) wastes processing time and slows down your architecture unnecessarily. - Throttling your script at the middleware level guarantees zero data loss during high-volume array migrations, smoothing out concurrency spikes automatically.
Frequently Asked Questions
Does the official Notion SDK handle rate limits automatically?
The official @notionhq/client package includes minor baseline configurations for network timeouts, but it does not natively queue or delay massive concurrent request batches. You are responsible for architecting your own backoff logic when pushing heavy data loads or paginating deep databases.
How long does the Notion API block requests after a 429 error?
Notion operates on a rolling window algorithm based on an average of 3 requests per second. Most rate limit blocks resolve within 1 to 2 seconds. However, severe bursts from multiple integrations on the same workspace can trigger temporary blocks lasting up to 60 seconds.
Can I use Promise.all to bypass the rate limit?
No. Using Promise.all executes your array of promises concurrently. Sending 50 page creation requests through Promise.all guarantees an immediate 429 response. You must process high-volume requests sequentially to respect the concurrency threshold.