Skip to content
Blog

How to Handle API Rate Limits in an n8n Node

A rate-limit-aware n8n node does four things well: it knows which responses are temporary, waits for the API's instruction when one exists, caps its retry work, and tells the workflow why a request stopped. A 429 response should not trigger an endless loop or an immediate second request.

Choose the retry boundary before writing the request code. A node can retry a safe operation a small number of times, or it can return a structured NodeApiError and let the workflow use Retry On Fail, Loop Over Items, or Wait. The right choice depends on whether the node owns the pacing policy and whether the operation can run again without creating a duplicate.

The examples below are patterns to adapt and run. Check them against your n8n version, the n8n-workflow types in your package, and the target API's documented limits before you ship them.

The decision flow for every failed request looks like this:

  1. Is the status 429 (or a status the API documents as temporary)? If not, throw NodeApiError and stop.
  2. Is the operation safe to repeat? If not, throw a rate-limited error with the wait hint and let the workflow decide.
  3. Are attempts left? If not, throw the final error with the service response attached.
  4. Wait for Retry-After if present, or a capped exponential backoff with jitter, then try again.

Define the API's rate-limit contract first

Rate limits differ by service, credential, endpoint, plan, and deployment. One endpoint may allow short bursts while another expects a steady request interval. Some services return 429 with Retry-After. Others return a reset timestamp, a quota code, or no wait instruction at all.

Write down the signals your node can trust before choosing a retry policy.

Response or signal What it usually means Safe node response
429 Too Many Requests The service is throttling this client or credential Wait using Retry-After when present, then retry only when the operation is safe
429 with a reset or quota header The service gives a time at which capacity returns Convert the value to a bounded wait or return it as a wait hint
403 with a documented quota body The quota is exhausted or the credential lacks permission Retry only when the API says the quota will reset; otherwise stop
401 or an invalid credential response Authentication cannot complete Stop and report a credential problem
400 or a validation response The request will fail again with the same input Stop and show the field or API message
500, 502, 503, or 504 The service may be unavailable for a short time Use a bounded retry when the operation is safe and the API permits it

Do not infer a rate limit from any failed request. The current n8n error reference says to declare a failure cause only when the response proves it. The same reference lists rate-limited, quota-exhausted, and temporarily-unavailable as separate causes.

The n8n rate-limit guide describes the workflow-level choices: Retry On Fail, Loop Over Items with Wait, and HTTP Request batching. A custom node should fit those choices instead of hiding an unbounded delay inside its execute method.

Choose where retries belong

There are three useful boundaries.

Retry inside the node

Use an internal retry loop when the node can identify a temporary response, the operation is safe to repeat, and a short wait keeps the workflow simple. A download or idempotent lookup often fits this model.

The node needs visible parameters or a documented policy for:

  • Maximum attempts
  • Maximum wait per attempt
  • Whether to respect Retry-After
  • Whether to add jitter so several executions do not retry together
  • Which status codes count as temporary
  • What happens after the final attempt

Keep the limits finite. A long sleep inside every item can hold a workflow open and make a rate problem harder to see.

Return the error to the workflow

Return a NodeApiError when the workflow should decide what happens next. This works well when users need to connect an error workflow, change batch size, or apply a different wait policy for different environments.

The n8n node settings include Retry On Fail for retrying a failed node after a wait. If your node already retries internally, document that interaction so users do not accidentally multiply the attempts.

Pace before the request

If the API publishes a request interval, send requests at that pace before the first 429. A node that processes many input items can expose a request interval or batch size and delay between calls. The HTTP Request node's batching option follows the same idea.

Pacing one execution does not create a shared limit across every workflow using the same credential. If several executions can hit the same account at once, a per-node sleep may still create bursts at the service. Use a shared queue or external rate limiter when the API requires a global policy.

Read rate-limit headers before you hit the limit

Many APIs tell you how much capacity is left on every successful response, not only on a 429. Common header names are X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset, and the IETF draft uses RateLimit and RateLimit-Policy. Ask the helper for the full response so you can read them:

const response = await this.helpers.httpRequestWithAuthentication.call(this, 'serviceApi', {
  ...requestOptions,
  returnFullResponse: true,
});

const remaining = Number(response.headers['x-ratelimit-remaining']);
const resetSeconds = Number(response.headers['x-ratelimit-reset']);

if (Number.isFinite(remaining) && remaining <= 1 && Number.isFinite(resetSeconds)) {
  // Reset is often epoch seconds; confirm the unit in the API docs.
  const waitMs = Math.min(Math.max(0, resetSeconds * 1000 - Date.now()), maximumWaitMs);
  await new Promise((resolve) => setTimeout(resolve, waitMs));
}

const body = response.body;

Header names and units differ between providers, and some send the reset as seconds-from-now rather than an epoch time. Read the provider's docs, then test with a real response. Long lists make this matter most, because a paginated loop can burn through a quota in seconds. The programmatic pagination guide shows where a check like this fits between pages.

Parse Retry-After without losing the response

The API may return a retry delay in a header or a reset value in its body. Normalize the response before creating the n8n error. Do not replace the original error with a generic "request failed" message that removes the status, headers, and service detail.

The exact error shape depends on the request helper and the API. The following helper accepts a numeric seconds value or an HTTP date and caps the result at a caller-provided maximum.

function parseRetryAfterMs(
  value: string | undefined,
  nowMs: number,
  maximumMs: number,
): number | undefined {
  if (!value) {
    return undefined;
  }

  const seconds = Number(value);
  const delayMs = Number.isFinite(seconds)
    ? seconds * 1000
    : Date.parse(value) - nowMs;

  if (!Number.isFinite(delayMs)) {
    return undefined;
  }

  return Math.min(Math.max(0, delayMs), maximumMs);
}

Keep the API's own contract in the node documentation. A service may use seconds, a date, or a custom reset field. If the header is missing or invalid, use a bounded backoff policy rather than treating the request as ready immediately.

Use a bounded retry loop for safe operations

A retry loop should record the attempt, wait before the next request, and stop when the maximum is reached. Add jitter to spread concurrent executions. The example below uses an exponential delay when the API does not provide Retry-After.

import { NodeApiError } from 'n8n-workflow';
import type {
  IExecuteFunctions,
  IHttpRequestOptions,
  JsonObject,
} from 'n8n-workflow';

const maxAttempts = 3;
const maximumWaitMs = 30_000;
const baseWaitMs = 1_000;

function backoffMs(attempt: number): number {
  const exponentialMs = baseWaitMs * 2 ** attempt;
  const jitterMs = Math.floor(Math.random() * 250);
  return Math.min(exponentialMs + jitterMs, maximumWaitMs);
}

async function requestWithBackoff(
  executeFunctions: IExecuteFunctions,
  credentialType: string,
  requestOptions: IHttpRequestOptions,
) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await executeFunctions.helpers.httpRequestWithAuthentication.call(
        executeFunctions,
        credentialType,
        requestOptions,
      );
    } catch (error) {
      const statusCode = Number(
        (error as { httpCode?: string | number }).httpCode,
      );

      if (statusCode !== 429 || attempt === maxAttempts - 1) {
        throw new NodeApiError(
          executeFunctions.getNode(),
          error as JsonObject,
        );
      }

      const headers = (
        (error as { response?: { headers?: Record<string, string> } })
          .response?.headers ?? {}
      );

      const retryAfterMs = parseRetryAfterMs(
        headers['retry-after'],
        Date.now(),
        maximumWaitMs,
      );

      const waitMs = retryAfterMs ?? backoffMs(attempt);

      await new Promise((resolve) => setTimeout(resolve, waitMs));
    }
  }

  throw new Error('Retry loop ended without a response');
}

This example treats only 429 as a retryable rate-limit response. Add 503 or another status only when the target API documents it as temporary and the operation can run again safely. Keep the final error as an API error so the execution shows the service response.

The programmatic-node error reference shows the request helper pattern and the NodeApiError constructor, plus the retryAfterMs and resetsAtEpochMs wait hints covered below. The n8n custom node errors guide shows how these errors look to the workflow user.

Retry only operations that can run again safely

A retry repeats the request. That is harmless for some operations and dangerous for others.

Operation type Default retry position What to verify
GET or HEAD lookup Usually safe The API may still charge each request or apply a read quota
Idempotent PUT or DELETE Often safe after checking API semantics The API's idempotency and partial-failure behavior
POST create action Do not retry by default An idempotency key or a service guarantee that duplicates are rejected
Upload or payment action Stop until the API provides an idempotency strategy Whether the server accepted the request before returning the error
Pagination request Usually safe to retry the same page The cursor or page token must remain valid

HTTP defines idempotent methods and their retry implications in RFC 9110. The API's own documentation still controls the final decision. A method name alone does not prove that a business action is safe to repeat.

If a service supports an idempotency key, generate or pass one consistently for the logical operation. Do not generate a new key for every retry when the API expects retries to share the original key.

Surface a structured rate-limit failure

When a retry cannot continue, give n8n a cause it can record. The n8n error-handling reference adds an optional failure declaration to NodeApiError and NodeOperationError with six causes, three of which (rate-limited, quota-exhausted, temporarily-unavailable) accept wait hints. It is a recent addition, so check that the n8n-workflow version your package builds against includes the failure option. On older versions, throw the same NodeApiError without it.

try {
  return await this.helpers.httpRequestWithAuthentication.call(
    this,
    credentialType,
    requestOptions,
  );
} catch (error) {
  const statusCode = Number(
    (error as { httpCode?: string | number }).httpCode,
  );

  if (statusCode === 429) {
    const headers =
      (error as { response?: { headers?: Record<string, string> } }).response?.headers ?? {};
    const retryAfterMs = parseRetryAfterMs(headers['retry-after'], Date.now(), maximumWaitMs);

    throw new NodeApiError(this.getNode(), error as JsonObject, {
      failure: {
        cause: 'rate-limited',
        ...(retryAfterMs === undefined ? {} : { retryAfterMs }),
      },
    });
  }

  throw new NodeApiError(this.getNode(), error as JsonObject);
}

Use quota-exhausted when the response proves that a quota must reset before the operation can run. Add resetsAtEpochMs when the service gives a reset time. Use temporarily-unavailable for a service outage that is not a rate limit. If the response does not prove the category, leave the failure declaration out.

A wait hint gives n8n structured information about when a request may work again. It does not turn an action node into a universal retry queue. Keep the node's own retry policy and the workflow's Retry On Fail setting visible to the person configuring it.

Pace batches without creating hidden work

A node that processes many input items can pace calls in the loop:

const requestIntervalMs = this.getNodeParameter(
  'requestIntervalMs',
  itemIndex,
) as number;

if (itemIndex > 0 && requestIntervalMs > 0) {
  await new Promise((resolve) => setTimeout(resolve, requestIntervalMs));
}

const result = await requestForItem(inputItems[itemIndex]);

This pattern is easy to understand, but it has limits:

  • It paces one execution, not every execution sharing the credential.
  • It increases execution time as the batch grows.
  • It can delay a request even when the API has capacity.
  • It does not replace handling for a 429 returned by the service.
  • It needs a maximum and a clear unit, such as milliseconds.

For a workflow with large input arrays, the user may get better control by sending batches through Loop Over Items and Wait. That moves the delay into visible workflow logic and lets the user tune it without rebuilding the package.

Test the retry contract before publishing

Test the node against a controlled API response or a safe mock. Record the n8n version, node package version, API contract, and retry parameters with each result.

Test Expected check
200 on first attempt One request, one output, no wait
429 with Retry-After seconds The delay uses the header and stays under the configured cap
429 with a reset date The date converts to a non-negative bounded wait
429 without a usable wait value Exponential backoff and jitter run, then the node stops at max attempts
403 quota response The node distinguishes quota exhaustion from invalid credentials
401 or 400 No blind retry; the error identifies the configuration or input problem
503 or 504 Retry only if the API and operation permit it
POST create or upload Duplicate protection is tested before enabling retries
Multiple input items Each item keeps its own request and output relationship
Continue On Fail A failed item is reported without hiding the item index
Several concurrent executions The test shows whether a per-execution delay is insufficient

The NativeShip credentials guide covers the credential boundary for authenticated requests. Use the NativeShip debugging guide to inspect attempts and wait behavior, then record the policy change in the node versioning guide.

Run these tests with npm run dev. From n8n 3.0, n8n-node dev runs n8n in a container, so install Docker or Podman, or use n8n-node dev --external-n8n with your own instance started with N8N_DEV_RELOAD=true.

If you generate a node from an OpenAPI definition, treat rate-limit behavior as a post-generation review item. The specification may describe a response, but the finished package still needs a tested retry boundary, safe operation policy, and user-facing error message.

Frequently asked questions

No. Retry only when the response proves temporary throttling, the operation is safe to repeat, and the node has a finite attempt and wait policy.

Use a bounded backoff with jitter if the operation is safe to repeat. If the operation is unsafe or the API contract is unclear, return the API error instead.

No. Retry On Fail is a workflow node setting. An internal loop runs inside the node. Document both so users do not multiply attempts by enabling both without understanding the total.

Use the rate-limited cause when the response proves request throttling, and quota-exhausted when a quota must reset first. Add retryAfterMs or resetsAtEpochMs only when the service gives a usable time. The failure option is recent, so check your n8n-workflow version supports it.

Only when the API provides idempotency or another duplicate-protection guarantee. A timeout does not prove that the server rejected the original request.

No. It paces that execution. Several workflows can still send a burst together, so a shared queue or rate limiter may be required.

Use controlled 429, 401, 403, 400, 503, and success responses, test safe and unsafe operations, record the n8n and package versions, and inspect the final error and retry counts.

Ready to launch your n8n, Make, Zapier or Activepieces integration?

Give your customers another way to use your product.

Get started