It was a night I'd set up a small automation: forty rows in a spreadsheet, each one handed to Claude to tidy up. The rows were store descriptions that needed polishing per language, and I was tired of doing it by hand. I pressed run, watched the modules light up green, and at row 14 the whole thing turned red and stopped.
I keep seeing the same story in no-code communities: "it works for a while, then it just stops." Nothing is broken on Claude's side. You've run into the per-minute limit on requests, and the answer comes back as a 429.
What I'd like to say up front is that the fix is simply to wait. The part worth writing down is that how long to wait isn't a guess. It's a subtraction, and there's one setting that decides whether failed rows get recorded or quietly disappear.
A 429 means "too fast," not "broken"
The Claude API limits requests per minute, plus input and output tokens per minute. Go over, and you get a 429. The response carries a retry-after header with a suggested wait.
How the limit is counted matters too. It isn't "everything resets at the top of the minute." Think of a bucket that refills a little at a time. The first several calls go through, the bucket runs dry, and 429s begin. Row 14 was exactly that moment.
You can check your own limits on the Limits page in the Console. To keep the numbers easy to swap for yours, everything below uses a mock with a limit of 10 requests per minute.
Seven spacing strategies on a mock bucket
This is a mock bucket running in virtual time, not the real API. The limit is 10 requests a minute, the bucket starts full with room for 10 calls, and each call takes 1.5 seconds. There are 40 rows. Because time is simulated, nothing actually waits.
# sim429.py — a mock bucket in virtual time (not the real API)
class Bucket:
def __init__(self, rpm, cap):
self.rate = rpm / 60.0 # refill per second
self.cap = cap # capacity at the start
self.tokens = cap
self.t = 0.0
def take(self, now):
# refill for the elapsed time, then see if one call fits
self.tokens = min(self.cap, self.tokens + (now - self.t) * self.rate)
self.t = now
if self.tokens >= 1:
self.tokens -= 1
return True
return False
def run(n, rpm, cap, latency, spacing=0.0, retry_wait=None,
max_attempts=3, stop_on_error=False):
b = Bucket(rpm, cap)
now = 0.0
calls = r429 = done = 0
for i in range(n):
attempts = 0
while True:
attempts += 1
calls += 1
ok = b.take(now)
now += latency # time the call itself takes
if ok:
done += 1
break
r429 += 1
if stop_on_error: # the "error stops everything" setting
return dict(done=done, calls=calls, r429=r429,
secs=round(now), stopped_at=i + 1)
if retry_wait is None or attempts >= max_attempts:
break # no retry or out of attempts: row stays empty
now += retry_wait
now += spacing # Sleep before the next row
return dict(done=done, calls=calls, r429=r429, secs=round(now))
N, RPM, CAP, LAT = 40, 10, 10, 1.5
print("A no spacing, stop on first 429", run(N, RPM, CAP, LAT, stop_on_error=True))
print("B no spacing, retry after 60 s", run(N, RPM, CAP, LAT, retry_wait=60))
print("C no spacing, ignore failures", run(N, RPM, CAP, LAT))
print("D Sleep 6.0 s per row", run(N, RPM, CAP, LAT, spacing=6.0))
print("E Sleep 4.5 s per row", run(N, RPM, CAP, LAT, spacing=4.5))Here is what came out.
| Setup | Rows done | 429s | Total time | Takeaway |
|---|---|---|---|---|
| A: no spacing, stop on error | 13 / 40 | 1 | 21 s | Stops at row 14 — the exact symptom |
| B: no spacing, retry after 60 s | 40 / 40 | 3 | 244 s | Everything lands, with three 60-second waits |
| C: no spacing, ignore failures | 19 / 40 | 21 | 60 s | The run stays green while 21 rows end up blank |
| D: Sleep 6.0 s per row | 40 / 40 | 0 | 300 s | No 429s, but a little over-cautious |
| E: Sleep 4.5 s per row | 40 / 40 | 0 | 240 s | No 429s, and 60 seconds faster than D |
C surprised me most. It finishes fastest and shows no error, yet 21 rows are empty, and you find out the next morning when you open the sheet. Something that stops is annoying; something that doesn't stop but quietly loses rows is far worse.
Sleep = 60 ÷ limit − the call's own duration
The gap between D and E is the main thing I wanted to show. With 10 requests a minute you need one call every 6 seconds. But those 6 seconds include the 1.5 seconds the call itself takes. Set Sleep to 6 and the real gap becomes 7.5.
Sleep seconds = 60 ÷ requests-per-minute limit − duration of one call
Example: 60 ÷ 10 − 1.5 = 4.5 secondsYou can read the call duration off Make's execution history by looking at the Claude module across a handful of runs. It grows with long outputs, so measuring beats guessing here.
I also like to add roughly ten percent of slack. If another scenario shares the same key, the bucket won't refill the way your arithmetic says. As an indie developer handling store text in several languages, I often have more than one job on the same API key, and exact calculations almost never hold.
Three places to touch in Make
Here is where each fix goes. Screen labels change with updates, so treat the names as a guide. The numbers in this article come from the mock above; I did not run an actual Make scenario for it.
| Goal | Where | How to think about it |
|---|---|---|
| Space out the rows | A Sleep module inside the loop, next to the Claude module | Use the formula's result. Sleep has a maximum, so split the batch if you need longer |
| Wait only on a 429 | Error handling on the Claude module: Break | Set the number of attempts and the interval; it can be shorter than 60 seconds |
| Don't drop failed rows | Choose Break, not Ignore | Keep incomplete executions stored so you can re-run them |
Ignore gives you scenario C. Break leaves the failed rows as incomplete executions, so you don't hunt for blank cells the next morning. After one stomach-sinking morning of exactly that, Break became my rule.
Decide the wait by division, and never ignore a failure — keep it. I hold to that line however many rows I add.
If it stops anyway, check in this order
- In the execution history, note which row stopped and which error code came back (429, or something else).
- If it's a 429, open the Console limits page and see whether requests per minute or tokens per minute is closer to the ceiling.
- If it's requests, adjust Sleep. If it's tokens, shorten what you send per call.
If you want to go deeper into reading retry-after in code, When Your Claude API Retry Logic Made Rate Limits Worse — The Retry-After Header You Forgot to Read covers it from the developer's side. For measuring headroom from headers in production, see When Claude API Suddenly Starts Returning 429 in Production.
To start, put one Sleep of four or five seconds next to the Claude module in your own scenario. That's where I started too.