Limits and quotas
Limits
Section titled “Limits”| Limit | Value | When it is exceeded |
|---|---|---|
Text per request in mp3, wav, pcm |
4,096 billable characters | 400 input_too_long |
Text per request in aac, opus, flac |
One chunk, about 900 billable characters | 400 input_too_long_for_format |
| Chunk size | 900 billable characters, cut at sentence ends | Longer text is spoken in several chunks and joined |
| Request body | 1 MB | 413 request_too_large |
speed |
0.7 to 1.3 | 400 invalid_speed |
| Requests per minute, per API key | 60 unless you set another, from 1 to 600 | 429 rate_limited with Retry-After |
| Requests in progress at once | 8 per account, across all your keys | 429 too_many_concurrent_requests |
| Monthly spending cap | Optional, per key | 402 key_spend_cap_reached |
The per-key and per-account limits are the API’s own, shared with chat, images and video: see Rate limits and quotas for how they count and how to retry. Listing voices and models is free: it never spends from the wallet.
Billable characters
Section titled “Billable characters”Limits and prices count billable characters: each character counts 1, spaces, punctuation and line breaks included, and each Chinese, Japanese or Korean character counts 3, because it speaks about three times as much audio. So 4,096 billable characters is 4,096 characters of English, or about 1,365 of Japanese. Each response says how many it counted in x-aderra-characters.
When the voice service is busy
Section titled “When the voice service is busy”Voices are generated by a voice service that all Aderra customers share. Under heavy load it can refuse a request with 429 provider_busy. Nothing is charged. Wait a moment and retry, with a growing delay between attempts.
The OpenAI SDKs already retry 429 with backoff (2 retries by default). For batch jobs, allow more:
client = OpenAI(base_url="https://api.aderra.ai/v1", api_key=os.environ["ADERRA_API_KEY"], max_retries=5)const client = new OpenAI({ baseURL: 'https://api.aderra.ai/v1', apiKey: process.env.ADERRA_API_KEY, maxRetries: 5,});If you send many requests at once, keep fewer than 8 in progress per account, or the extra ones get 429 too_many_concurrent_requests.
Text longer than 4,096 characters
Section titled “Text longer than 4,096 characters”Split the text at sentence ends into pieces of at most 4,096 billable characters, speak each piece, and join the audio. MP3 files join by putting their bytes one after another; this helper also drops the tag at the start of every file but the first, as Aderra does when it joins chunks:
import os, refrom openai import OpenAI
client = OpenAI(base_url="https://api.aderra.ai/v1", api_key=os.environ["ADERRA_API_KEY"], max_retries=5)
LIMIT = 4096CJK = re.compile("[-ヿ㐀-䶿一-鿿가-豈-ヲ-゚]")
def billable(text): """Characters as Aderra counts them: Chinese, Japanese and Korean count 3.""" return sum(3 if CJK.match(ch) else 1 for ch in text)
def split_text(text, limit=LIMIT): """Pieces of at most `limit` billable characters, cut after sentence ends.""" pieces, current = [], "" for sentence in re.split(r"(?<=[.!?。!?\n])", text): if billable(current + sentence) > limit: pieces.append(current) current = "" while billable(sentence) > limit: # one sentence over the limit (rare) end, n = 0, 0 while n + billable(sentence[end]) <= limit: n += billable(sentence[end]) end += 1 space = sentence.rfind(" ", 0, end) end = space + 1 if space > 0 else end pieces.append(sentence[:end]) sentence = sentence[end:] current += sentence pieces.append(current) return [p.strip() for p in pieces if p.strip()]
def without_id3(mp3): """Drops a leading ID3 tag, so the joined file has only the first one.""" if len(mp3) < 10 or mp3[:3] != b"ID3": return mp3 size = (mp3[6] & 127) << 21 | (mp3[7] & 127) << 14 | (mp3[8] & 127) << 7 | mp3[9] & 127 return mp3[10 + size + (10 if mp3[5] & 16 else 0):]
def speak_long(text, voice, path): with open(path, "wb") as out: for i, piece in enumerate(split_text(text)): audio = client.audio.speech.create( model="aderra-voice-1", voice=voice, input=piece) mp3 = audio.read() out.write(mp3 if i == 0 else without_id3(mp3))
# speak_long(open("chapter.txt").read(), "Daniel", "chapter.mp3")- Each piece is a separate request, billed for its own characters: splitting does not change the total price.
- Each piece is spoken on its own, so the intonation starts afresh at every join. Cutting at sentence ends keeps the joins natural; cutting at paragraph ends is better still, if your text has them.
- For exact joins in any tool, ask for
pcminstead, join the bytes, and wrap them once in a WAV header (see Audio formats).