Skip to content

Rate limits keep the API fast and reliable for everyone. Every API key has the same limits, whatever your plan: the API is open on every plan, Free included, and only your API credit sets how much you can generate.

​Limits

LimitEvery API key
Requests per minute600
Requests per day1,000
Requests at once20
Words per voice-over, tone directions included5,000
Requests per minute from one IP address600
Request body25 MB

Your key's limits are also on the Developer Dashboard, and in the key.rateLimit field of GET /me.

​What counts

A request counts toward your requests per minute and per day once it succeeds. Requests that fail, whatever the reason, don't count. Only voice-overs count toward your requests at once, from the moment Voice213 accepts them until they end, whether they succeed or fail.

EndpointChecked against the per-minute and per-day limitsCounts toward themCounts toward requests at once
POST /ttsYesYes, when it succeedsYes, while it runs
GET /meYesNoNo
POST /tts/estimate, GET /models, GET /tts/voices, GET /tts/tonesNoNoNo

​Requests per minute

The minute starts with the first request that counts. Once your key has made 600 requests within that minute, further requests get 429 until 60 seconds have passed since it started:

{
  "error": "Rate limit exceeded: RPM limit exceeded",
  "limit": 600
}

This 429 has no Retry-After header: wait until the minute is over, then retry.

​Requests per day

Once your key has made 1,000 requests in a day, further requests get 429 until the day ends, at midnight server time:

{
  "error": "Rate limit exceeded: Daily limit exceeded",
  "limit": 1000
}

Don't retry this one in a loop: it won't clear until the next day. If you regularly need more, contact us.

​Requests at once

One key can have up to 20 voice-overs (POST /tts) running at the same time. A voice-over runs from the moment Voice213 accepts it until its audio is ready or it fails. Once 20 are running, the next one gets 429:

{
  "error": "Rate limit exceeded: Concurrent request limit exceeded",
  "limit": 20
}
  • A voice-over your client cancels or stops waiting for keeps running until Voice213 finishes it (see When your client gives up), and keeps its place until then. A quick retry after a timeout can therefore hit this limit.
  • Estimates, GET /me and the catalogue endpoints don't count, and are never refused by it.
  • This 429 has no Retry-After header and doesn't count toward your per-minute or per-day limits. Retry once one of your running voice-overs has ended.

A queue with a fixed number of workers, like the example below, keeps you under it.

​Requests per IP address

The whole API also accepts up to 600 requests a minute from one IP address. Every response under /api carries the standard headers for this limit:

RateLimit-Policy: 600;w=60
RateLimit-Limit: 600
RateLimit-Remaining: 597
RateLimit-Reset: 42

Past it, requests get 429 with {"error": "RATE_LIMITED"} and a Retry-After header giving the seconds to wait.

​Text length

  • Words per request: up to 5,000 in POST /tts, in both formats, and in Estimate Cost. Every word counts, tone directions included. Longer texts get 400 Max 5000 words exceeded, with the words you sent and the maxWords allowed. Long texts also take longer to generate, so split very long scripts.
  • Something to speak: a text made only of tone directions has nothing to speak, and gets 400 EMPTY_TEXT.
  • Unspoken text: tone directions, spaces and invisible characters stay free, but they can't outweigh the spoken text. A text may be at most twice its billable characters (counted as at least 100) plus 1,000 characters long, every character counted. Longer texts get 400 TEXT_TOO_LONG, with the chars you sent and the maxChars allowed. See Too much unspoken text.
  • Request body: up to 25 MB, for every endpoint.

​Staying within the limits

Run requests through a queue, and space them out rather than sending bursts:

import asyncio
import os

import httpx

MAX_PARALLEL = 5  # how many requests you run at once (20 at most)
semaphore = asyncio.Semaphore(MAX_PARALLEL)


async def synthesize(client, text):
    async with semaphore:
        for attempt in range(5):
            response = await client.post(
                "https://api.voice213.com/api/v1/tts",
                headers={"Authorization": f"Bearer {os.environ['VOICE213_API_KEY']}"},
                json={"text": text, "model": "voice213-lite", "voice": "nariman"},
            )
            if response.status_code != 429:
                response.raise_for_status()
                return response.json()
            if "Daily limit" in response.json().get("error", ""):
                response.raise_for_status()  # won't clear today
            await asyncio.sleep(float(response.headers.get("Retry-After", 15 * (attempt + 1))))
        response.raise_for_status()


async def main(texts):
    async with httpx.AsyncClient(timeout=300) as client:
        return await asyncio.gather(*(synthesize(client, t) for t in texts))
  • Combine short texts into one request where it makes sense. One request of 1,000 words counts once; twenty requests of 50 words count twenty times. Every request is also billed at least 100 characters, so very short requests cost more per character.
  • Save audio you generate more than once instead of generating it again.
  • Read the free catalogue endpoints (/models, /tts/voices, /tts/tones) once and keep the result: they rarely change.

Was this page helpful?