Originally published on OperantSolo, which also has the free demo workflow.
An n8n workflow that calls an API will hit HTTP 429 sooner or later. The common fix is to add a Wait node with a number you picked. That works until the provider asks for a longer pause, or until a retry repeats something that already succeeded.
Here is a more careful way to handle it.
Quick answer
Inspect the 429 response, slow the request rate, and honor a valid Retry-After header before retrying. Keep the original job, cap the number of attempts, and retry writes only when repeating them is safe.
Four fixes, in order
Look at the response first. In the HTTP Request node, turn on Include Response Headers and Status. Read the status, the body and any hint about which quota you hit. A 429 can mean a per-second burst, a daily cap or a token limit, and the right fix differs.
Reduce the burst. Lower the batch size and space the requests to what the provider documents. Start with Items per Batch set to 1 and a batch interval that fits the quota. (A 1,000 ms interval is only an example; use the provider's number.)
Wait the amount the provider asks for. Retry-After can be a number of seconds or an HTTP date. Treat a valid value as a minimum, not a target to shrink. If you add jitter, add it on top of the minimum, never subtract from it. If the header is missing or invalid, fall back to a bounded backoff (the lab uses 2, 4 and 8 seconds, capped at 60).
Protect the job. Keep the job ID through the whole loop, count total attempts (four attempts means four in total, including the first), and stop or defer when the wait is too long. The lab defers anything over 300 seconds.
The part people miss: writes
A POST that timed out may still have been accepted. If you retry it blindly you can create a duplicate. Retry writes only when you have an idempotency key or a reliable duplicate check.
Why native Retry On Fail is not enough here
n8n's built-in Retry On Fail uses a fixed Wait Between Tries. It does not read Retry-After. That is fine for a flaky network call. For rate limits you usually want your own loop that reads the header, so turn native retry off when you build one.
For the HTTP Request node in a custom loop:
Enable Include Response Headers and Status and Never Error, so a 429 reaches your logic instead of stopping the run.
Reattach the job context after the response (an itemMatching adapter does this).
For the wait, a Wait node set to seconds with the expression {{ $json.waitSeconds }}.
I built a small workflow that simulates a 429 followed by a 200, so you can watch the loop without touching a real API.
Download the demo and see the full walkthrough.
I built a small workflow that simulates a 429 followed by a 200, so you can watch the loop without touching a real API. A Code node decides between five outcomes: success, retry, stop, review or defer.
A 401 or 503 results in stop, because this policy only handles 429.
Expected result: PASS after the simulated 429, with a real wait of at least two seconds.
What was tested, and what was not
An 18-case deterministic policy test passed in n8n Cloud.
A separate local sweep of 28 delay and jitter combinations produced no wait below the provider minimum.
In the timed run, a simulated Retry-After: 2 followed by a 200 showed a gap of 2.038 seconds, and the job ID was kept.
Environment: n8n Cloud 2.43.0, no credentials, no external requests.
These are correctness checks on simulated responses. They are not speed or reliability benchmarks, and they say nothing about a live provider's capacity.
Limits
No shared rate limiter. If several workflows share one quota, a Wait node in one of them cannot coordinate with the rest.
No durable queue. Deferred jobs need to be stored somewhere with a "not before" time, and the demo has no dispatcher for that.
Not meant for financial writes, bulk publishing or high-volume production traffic without more work.
If 429s continue after adding a Wait
Check these before blaming the delay: several workflows are sharing the quota, the batches are still too large, the limit counts something else (tokens or writes), the request is being rejected for another reason, or your loop restarts work that already succeeded.
The demo workflow and the full walkthrough with screenshots are on the original article: n8n rate limit: fix HTTP 429 with Retry-After.
If you have a 429 case that behaves differently, say what the API returns in the comments. I'd like to see it.
Top comments (0)