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
// src/elevenlabs/rate-limiter.ts
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,
// Each queued request adds ~50ms to response time
// so keep queue depth reasonable
timeout: 120_000, // 2 minute timeout per request
throwOnTimeout: true,
});
queue.on("error", (error) => {
console.error("[ElevenLabs Queue] Request failed:", error.message);
});
return queue;
}
// Usage
const queue = createRequestQueue("pro"); // 10 concurrent
async function generateWithQueue(voiceId: string, text: string) {
return queue.add(async () => {
return client.textToSpeech.convert(voiceId, {
text,
model_id: "eleven_flash_v2_5",
});
});
}
// All 20 requests run with max 10 concurrent
const results = await Promise.all(
texts.map(text => generateWithQueue("21m00Tcm4TlvDq8ikWAM", text))
);
Step 4: Exponential Backoff for system_busy
// src/elevenlabs/backoff.ts
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;
// Don't retry non-retryable errors
if (status === 401 || status === 400 || status === 404) throw error;
// For concurrent limit, retry immediately (queue handles spacing)
if (errorType === "too_many_concurrent_requests") {
if (attempt === config.maxRetries) throw error;
// Short pause — the queue is managing concurrency
await new Promise(r => setTimeout(r, 50 * (attempt + 1)));
continue;
}
// For system_busy or 5xx, exponential backoff with jitter
if (attempt === config.maxRetries) throw error;
const exponentialDelay = config.baseDelayMs * Math.pow(2, attempt);
const jitter = Math.random() * config.jitterMs;
const delay = Math.min(exponentialDelay + jitter, config.maxDelayMs);
console.warn(`[ElevenLabs] ${errorType || status}. Retry ${attempt + 1}/${config.maxRetries} in ${delay.toFixed(0)}ms`);
await new Promise(r => setTimeout(r, delay));
}
}
throw new Error("Unreachable");
}
Step 5: Quota Monitor
// src/elevenlabs/quota-monitor.ts
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.characterCount = user.subscription.character_count;
this.characterLimit = user.subscription.character_limit;
this.lastCheck = now;
}
const remaining = this.characterLimit - this.characterCount;
const pctUsed = (this.characterCount / this.characterLimit) * 100;
return {
used: this.characterCount,
limit: this.characterLimit,
remaining,
pctUsed: Math.round(pctUsed * 10) / 10,
warning: pctUsed >= this.warningThresholdPct,
};
}
async guardRequest(textLength: number): Promise<void> {
const quota = await this.check();
if (textLength > quota.remaining) {
throw new Error(
`Insufficient quota: need ${textLength} chars, have ${quota.remaining} remaining (${quota.pctUsed}% used)`
);
}
if (quota.warning) {
console.warn(`[ElevenLabs] Quota warning: ${quota.pctUsed}% used (${quota.remaining} chars remaining)`);
}
}
}
Step 6: Combined Rate-Limited Client
// src/elevenlabs/resilient-client.ts
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 }); // We handle retries
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(() =>
withBackoff(() =>
client.textToSpeech.convert(voiceId, {
text,
model_id: modelId,
})
)
);
},
getQueueStats() {
return {
pending: queue.pending,
size: queue.size,
};
},
checkQuota: () => quota.check(),
};
}
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.
1---2name: elevenlabs-rate-limits3description: |4 Implement ElevenLabs rate limiting, concurrency queuing, and backoff patterns.5 Use when handling 429 errors, implementing retry logic,6 or managing concurrent TTS request throughput.7 Trigger: "elevenlabs rate limit", "elevenlabs throttling",8 "elevenlabs 429", "elevenlabs retry", "elevenlabs backoff",9 "elevenlabs concurrent requests".10allowed-tools: Read, Write, Edit11version: 1.0.012license: MIT13author: Jeremy Longshore <jeremy@intentsolutions.io>14tags: [saas, voice, ai, elevenlabs, rate-limits, reliability]15compatible-with: claude-code16---1718# ElevenLabs Rate Limits1920## Overview2122Handle 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.2324## Prerequisites2526- ElevenLabs SDK installed27- Understanding of your subscription plan's limits28- `p-queue` package (recommended): `npm install p-queue`2930## Instructions3132### Step 1: Understand the Two 429 Error Types3334ElevenLabs returns HTTP 429 for two different reasons:3536| 429 Variant | Response Body | Cause | Strategy |37|-------------|--------------|-------|----------|38| `too_many_concurrent_requests` | `{"detail":{"status":"too_many_concurrent_requests"}}` | Exceeded plan concurrency | Queue requests, don't backoff |39| `system_busy` | `{"detail":{"status":"system_busy"}}` | Server overload | Exponential backoff |4041### Step 2: Plan Concurrency Limits4243| Plan | Max Concurrent Requests | Characters/Month |44|------|------------------------|-------------------|45| Free | 2 | 10,000 |46| Starter | 3 | 30,000 |47| Creator | 5 | 100,000 |48| Pro | 10 | 500,000 |49| Scale | 15 | 2,000,000 |50| Business | 15 | Custom |5152### Step 3: Concurrency-Aware Request Queue5354```typescript55// src/elevenlabs/rate-limiter.ts56import PQueue from "p-queue";5758type ElevenLabsPlan = "free" | "starter" | "creator" | "pro" | "scale" | "business";5960const CONCURRENCY_LIMITS: Record<ElevenLabsPlan, number> = {61 free: 2,62 starter: 3,63 creator: 5,64 pro: 10,65 scale: 15,66 business: 15,67};6869export function createRequestQueue(plan: ElevenLabsPlan) {70 const concurrency = CONCURRENCY_LIMITS[plan];7172 const queue = new PQueue({73 concurrency,74 // Each queued request adds ~50ms to response time75 // so keep queue depth reasonable76 timeout: 120_000, // 2 minute timeout per request77 throwOnTimeout: true,78 });7980 queue.on("error", (error) => {81 console.error("[ElevenLabs Queue] Request failed:", error.message);82 });8384 return queue;85}8687// Usage88const queue = createRequestQueue("pro"); // 10 concurrent8990async function generateWithQueue(voiceId: string, text: string) {91 return queue.add(async () => {92 return client.textToSpeech.convert(voiceId, {93 text,94 model_id: "eleven_flash_v2_5",95 });96 });97}9899// All 20 requests run with max 10 concurrent100const results = await Promise.all(101 texts.map(text => generateWithQueue("21m00Tcm4TlvDq8ikWAM", text))102);103```104105### Step 4: Exponential Backoff for system_busy106107```typescript108// src/elevenlabs/backoff.ts109export async function withBackoff<T>(110 operation: () => Promise<T>,111 config = {112 maxRetries: 5,113 baseDelayMs: 1000,114 maxDelayMs: 32_000,115 jitterMs: 500,116 }117): Promise<T> {118 for (let attempt = 0; attempt <= config.maxRetries; attempt++) {119 try {120 return await operation();121 } catch (error: any) {122 const status = error.statusCode || error.status;123 const errorType = error.body?.detail?.status;124125 // Don't retry non-retryable errors126 if (status === 401 || status === 400 || status === 404) throw error;127128 // For concurrent limit, retry immediately (queue handles spacing)129 if (errorType === "too_many_concurrent_requests") {130 if (attempt === config.maxRetries) throw error;131 // Short pause — the queue is managing concurrency132 await new Promise(r => setTimeout(r, 50 * (attempt + 1)));133 continue;134 }135136 // For system_busy or 5xx, exponential backoff with jitter137 if (attempt === config.maxRetries) throw error;138139 const exponentialDelay = config.baseDelayMs * Math.pow(2, attempt);140 const jitter = Math.random() * config.jitterMs;141 const delay = Math.min(exponentialDelay + jitter, config.maxDelayMs);142143 console.warn(`[ElevenLabs] ${errorType || status}. Retry ${attempt + 1}/${config.maxRetries} in ${delay.toFixed(0)}ms`);144 await new Promise(r => setTimeout(r, delay));145 }146 }147 throw new Error("Unreachable");148}149```150151### Step 5: Quota Monitor152153```typescript154// src/elevenlabs/quota-monitor.ts155import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";156157export class QuotaMonitor {158 private characterCount = 0;159 private characterLimit = 0;160 private lastCheck = 0;161162 constructor(163 private client: ElevenLabsClient,164 private warningThresholdPct = 80,165 private checkIntervalMs = 60_000166 ) {}167168 async check(): Promise<{169 used: number;170 limit: number;171 remaining: number;172 pctUsed: number;173 warning: boolean;174 }> {175 const now = Date.now();176 if (now - this.lastCheck > this.checkIntervalMs) {177 const user = await this.client.user.get();178 this.characterCount = user.subscription.character_count;179 this.characterLimit = user.subscription.character_limit;180 this.lastCheck = now;181 }182183 const remaining = this.characterLimit - this.characterCount;184 const pctUsed = (this.characterCount / this.characterLimit) * 100;185186 return {187 used: this.characterCount,188 limit: this.characterLimit,189 remaining,190 pctUsed: Math.round(pctUsed * 10) / 10,191 warning: pctUsed >= this.warningThresholdPct,192 };193 }194195 async guardRequest(textLength: number): Promise<void> {196 const quota = await this.check();197 if (textLength > quota.remaining) {198 throw new Error(199 `Insufficient quota: need ${textLength} chars, have ${quota.remaining} remaining (${quota.pctUsed}% used)`200 );201 }202 if (quota.warning) {203 console.warn(`[ElevenLabs] Quota warning: ${quota.pctUsed}% used (${quota.remaining} chars remaining)`);204 }205 }206}207```208209### Step 6: Combined Rate-Limited Client210211```typescript212// src/elevenlabs/resilient-client.ts213import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";214import { createRequestQueue } from "./rate-limiter";215import { withBackoff } from "./backoff";216import { QuotaMonitor } from "./quota-monitor";217218export function createResilientClient(plan: "free" | "starter" | "creator" | "pro" | "scale" = "pro") {219 const client = new ElevenLabsClient({ maxRetries: 0 }); // We handle retries220 const queue = createRequestQueue(plan);221 const quota = new QuotaMonitor(client);222223 return {224 async generateSpeech(voiceId: string, text: string, modelId = "eleven_multilingual_v2") {225 await quota.guardRequest(text.length);226227 return queue.add(() =>228 withBackoff(() =>229 client.textToSpeech.convert(voiceId, {230 text,231 model_id: modelId,232 })233 )234 );235 },236237 getQueueStats() {238 return {239 pending: queue.pending,240 size: queue.size,241 };242 },243244 checkQuota: () => quota.check(),245 };246}247```248249## Model Cost Impact on Quota250251| Model | Credits per Character | 10,000 Chars Cost |252|-------|-----------------------|-------------------|253| `eleven_v3` | 1.0 | 10,000 credits |254| `eleven_multilingual_v2` | 1.0 | 10,000 credits |255| `eleven_flash_v2_5` | 0.5 | 5,000 credits |256| `eleven_turbo_v2_5` | 0.5 | 5,000 credits |257258Use Flash/Turbo models during development to conserve quota.259260## Error Handling261262| Scenario | Detection | Response |263|----------|-----------|----------|264| Concurrent limit hit | 429 + `too_many_concurrent_requests` | Queue; retry after ~50ms per queued request |265| System busy | 429 + `system_busy` | Exponential backoff (1s, 2s, 4s, 8s...) |266| Quota exhausted | 401 + `quota_exceeded` | Stop requests; alert; wait for reset |267| Server error | 500-599 | Exponential backoff; max 5 retries |268269## Resources270271- [ElevenLabs Rate Limits Help](https://help.elevenlabs.io/hc/en-us/articles/19571824571921)272- [ElevenLabs Pricing](https://elevenlabs.io/pricing)273- [p-queue Documentation](https://github.com/sindresorhus/p-queue)274275## Next Steps276277For security configuration, see `elevenlabs-security-basics`.278