ElevenLabs Rate Limits
Overview
Handle ElevenLabs rate limits with plan-aware concurrency queuing, exponential backoff, and quota monitoring. ElevenLabs uses two rate limit mechanisms: concurrent request limits (per plan) and system-level throttling.
Prerequisites
- ElevenLabs SDK installed
- Understanding of your subscription plan's limits
p-queue package (recommended): npm install p-queue
Instructions
Step 1: Understand the Two 429 Error Types
ElevenLabs returns HTTP 429 for two different reasons:
| 429 Variant | Response Body | Cause | Strategy |
|---|
too_many_concurrent_requests | {"detail":{"status":"too_many_concurrent_requests"}} | Exceeded plan concurrency | Queue requests, don't backoff |
system_busy | {"detail":{"status":"system_busy"}} | Server overload | Exponential backoff |
Step 2: Plan Concurrency Limits
| Plan | Max Concurrent Requests | Characters/Month |
|---|
| Free | 2 | 10,000 |
| Starter | 3 | 30,000 |
| Creator | 5 | 100,000 |
| Pro | 10 | 500,000 |
| Scale | 15 | 2,000,000 |
| Business | 15 | Custom |
Step 3: Concurrency-Aware Request Queue
import PQueue from "p-queue";
type ElevenLabsPlan = "free" | "starter" | "creator" | "pro" | "scale" | "business";
const CONCURRENCY_LIMITS: Record<ElevenLabsPlan, number> = {
free: 2,
starter: 3,
creator: 5,
pro: 10,
scale: 15,
business: 15,
};
export function createRequestQueue(plan: ElevenLabsPlan) {
const concurrency = CONCURRENCY_LIMITS[plan];
const queue = new PQueue({
concurrency,
timeout: 120_000,
throwOnTimeout: true,
});
queue.on("error", (error) => {
console.error("[ElevenLabs Queue] Request failed:", error.message);
});
return queue;
}
const queue = createRequestQueue("pro");
async function generateWithQueue(voiceId: string, text: string) {
return queue.add(async () => {
return client.textToSpeech.convert(voiceId, {
text,
model_id: "eleven_flash_v2_5",
});
});
}
const results = await Promise.all(
texts.map(text => generateWithQueue("21m00Tcm4TlvDq8ikWAM", text))
);
Step 4: Exponential Backoff for system_busy
export async function withBackoff<T>(
operation: () => Promise<T>,
config = {
maxRetries: 5,
baseDelayMs: 1000,
maxDelayMs: 32_000,
jitterMs: 500,
}
): Promise<T> {
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
try {
return await operation();
} catch (error: any) {
const status = error.statusCode || error.status;
const errorType = error.body?.detail?.status;
if (status === 401 || status === 400 || status === 404) throw error;
if (errorType === "too_many_concurrent_requests") {
if (attempt === config.maxRetries) throw error;
await new ( (r, * (attempt + )));
;
}
(attempt === config.) error;
exponentialDelay = config. * .(, attempt);
jitter = .() * config.;
delay = .(exponentialDelay + jitter, config.);
.();
( (r, delay));
}
}
();
}
Step 5: Quota Monitor
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
export class QuotaMonitor {
private characterCount = 0;
private characterLimit = 0;
private lastCheck = 0;
constructor(
private client: ElevenLabsClient,
private warningThresholdPct = 80,
private checkIntervalMs = 60_000
) {}
async check(): Promise<{
used: number;
limit: number;
remaining: number;
pctUsed: number;
warning: boolean;
}> {
const now = Date.now();
if (now - this.lastCheck > this.checkIntervalMs) {
const user = await this.client.user.get();
this. = user..;
. = user..;
. = now;
}
remaining = . - .;
pctUsed = (. / .) * ;
{
: .,
: .,
remaining,
: .(pctUsed * ) / ,
: pctUsed >= .,
};
}
(: ): <> {
quota = .();
(textLength > quota.) {
(
);
}
(quota.) {
.();
}
}
}
Step 6: Combined Rate-Limited Client
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import { createRequestQueue } from "./rate-limiter";
import { withBackoff } from "./backoff";
import { QuotaMonitor } from "./quota-monitor";
export function createResilientClient(plan: "free" | "starter" | "creator" | "pro" | "scale" = "pro") {
const client = new ElevenLabsClient({ maxRetries: 0 });
const queue = createRequestQueue(plan);
const quota = new QuotaMonitor(client);
return {
async generateSpeech(voiceId: string, text: string, modelId = "eleven_multilingual_v2") {
await quota.guardRequest(text.length);
return queue.add(() =>
(
client..(voiceId, {
text,
: modelId,
})
)
);
},
() {
{
: queue.,
: queue.,
};
},
: quota.(),
};
}
Model Cost Impact on Quota
| Model | Credits per Character | 10,000 Chars Cost |
|---|
eleven_v3 | 1.0 | 10,000 credits |
eleven_multilingual_v2 | 1.0 | 10,000 credits |
eleven_flash_v2_5 | 0.5 | 5,000 credits |
eleven_turbo_v2_5 | 0.5 | 5,000 credits |
Use Flash/Turbo models during development to conserve quota.
Error Handling
| Scenario | Detection | Response |
|---|
| Concurrent limit hit | 429 + too_many_concurrent_requests | Queue; retry after ~50ms per queued request |
| System busy | 429 + system_busy | Exponential backoff (1s, 2s, 4s, 8s...) |
| Quota exhausted | 401 + quota_exceeded | Stop requests; alert; wait for reset |
| Server error | 500-599 | Exponential backoff; max 5 retries |
Resources
Next Steps
For security configuration, see elevenlabs-security-basics.