"429 Too Many Requests" Error
How We Squashed the Dreaded "429 Too Many Requests" Error
If you've ever built an application that needs to talk to an external API, you've probably run into the dreaded 429 Too Many Requests error. It's one of the most frustrating errors to debug because your code isn't wrong—it's just too fast.
We recently ran into this exact problem while developing our AI Film Director's Assistant. Here’s a quick breakdown of what caused it and the strategies we used to fix it for good.
What is a 429 Error?
Simply put, a 429 error is the API's "stop" sign. It's the server politely (or not-so-politely) telling you, "You're sending me requests faster than I can handle. Please slow down."
This isn't a bug in your code's logic, but a problem with your code's behavior. Most APIs have rate limits—like "30 requests per minute"—to protect themselves from being overwhelmed.
The Culprits: A Race Condition and a Flood
We had two main sources for this error:
- The "Test Voice" Button: In our casting section, it was easy for a user to rapidly click the "Test Voice" button multiple times, firing off a new API call with every click. 
- The "Assemble First Cut" Button: This was the big one. When a user clicked this, our player would try to be smart and fetch the audio for every single line of dialogue in the script all at once. If a script had 40 lines of dialogue, it fired 40 API requests simultaneously. The server would see this flood of requests and immediately shut us down with a - 429.
The Fix, Part 1: The API Queue (Serialization)
The most important fix was to stop the flood. We implemented an API "queue" using a promise chain.
Think of it like this:
- The Broken Way: We were telling 40 people to rush a single doorway at the same time. 
- The Fixed Way: We created a single-file line and told person #2 they couldn't go until person #1 was through the door. 
In JavaScript, this looks something like this:
// A promise that all future calls will chain onto
let apiQueue = Promise.resolve();
async function enqueueApiCall(fn) {
  // Chain the next function (fn) to the end of the queue
  apiQueue = apiQueue.then(() => fn());
  return apiQueue;
}
Now, when we pre-load our 40 audio files, we wrap each fetchDialogueAudio(line) call in enqueueApiCall(). The result? The 40 requests go out one by one, with a clean gap between them. The 429 errors from the player vanished.
The Fix, Part 2: Pre-loading and Exponential Backoff
To make the player experience smooth, we combined our new queue with a pre-loading step.
- Pre-loading: When the user clicks "Assemble First Cut," we now show a loading bar that says "Assembling Audio (X%)...". Behind the scenes, we are running all 40 audio requests through our new, safe queue. It takes a moment up front, but once the player opens, all the audio is in the browser's cache, so playback is instant and error-free. 
- Exponential Backoff: For any individual request that might fail (even in the queue), we added a retry function. If a request gets a 429, it waits 1 second and tries again. If it fails again, it waits 2 seconds, then 4, then 8... This "backs off" politely to give the server a break. 
Conclusion
That 429 error was a perfect lesson. The feature wasn't broken, our approach was. By serializing our requests with a promise queue and improving the user experience with a pre-loader, we created a stable, robust feature that works with the API, not against it.
 
                        