Go live
Job concurrency limits
Job concurrency limits for the Inworld TTS async and batch synthesis APIs.
Async and batch synthesis jobs have separate limits from realtime TTS because they use capacity differently. A realtime generation holds a slot for the fraction of a second needed to synthesize. A job holds a slot from submission until it finishes, which can take minutes or hours.
Realtime generations, async jobs, and batch jobs are counted in three independent pools. Saturating one never consumes another, so a full batch queue cannot slow down your realtime traffic.
Default limits
| Plan | Concurrent async jobs | Concurrent batch jobs | Queued characters (batch) |
|---|---|---|---|
| On-Demand | 5 | 5 | 1,000,000 |
| Creator | 10 | 10 | 2,000,000 |
| Builder | 50 | 50 | 10,000,000 |
| Developer | 150 | 150 | 30,000,000 |
| Growth | 500 | 500 | 100,000,000 |
| Enterprise | Custom | Custom | Custom |
Requests above these limits are rejected rather than queued. Other TTS requests are limited separately — see Concurrency limits.
How job slots are counted
- A slot is taken at submit, when the operation is created — not when synthesis actually begins.
- It is released when the operation reaches a terminal state, whether the job succeeded or failed.
- A job that stops without reporting a terminal state — an infrastructure failure, for example — does not strand its slot. On the next submit that would exceed your limit, the service checks whether the jobs holding slots are still running. It reclaims slots from any jobs that are not. The reclaimed slots return on the same attempt that would otherwise have been rejected. A backstop expiry covers anything that check misses: 12 hours for async, 24 hours for batch.
- Submissions over the limit are rejected outright. No operation is created and nothing is queued on your behalf, so a rejected submit costs you nothing and the identical request can be sent again later.
Queued characters (batch only)
Batch jobs carry a second budget, counted in characters rather than jobs.
- What counts: the total
textacross every item, summed over all of your batches that have not yet reached a terminal state. - Your budget: the per-plan value in the table above.
- Release: the reservation is freed when the batch reaches a terminal state, or swept 24 hours after it was made. Unlike the job slot, this budget has only that time-based backstop.
A submission that would exceed the budget is rejected in full. No operation is created, no characters are reserved, and no partial synthesis occurs.
Pack items into one batch
A single batch holds up to 10,000 items. Each batch in flight holds a concurrency slot, so packing items into fewer, larger batches leaves more of your budget free.
Balance that against turnaround. A larger batch takes proportionally longer to finish. Where the work divides naturally, several batches finish sooner than one very large one because they are scheduled independently, at the same per-character rate. Pack items together to save slots; split when one batch would grow large enough to hold up the rest. See Turnaround.
Per-job size on the On-Demand plan
On-Demand accounts are limited to 10,000 characters per job — the text of an async job, or the total across all items of a batch. Other plans get only the service-wide size ceilings: 100,000 characters per async job, and the same per item within a batch.
This is a ceiling on each individual job, not a total. On-Demand accounts can keep as many jobs in flight as their concurrency allows.
Other plans have no fixed per-batch total, but that is not the same as unlimited: a batch still has to fit inside the request size, and inside your queued-character budget on its own. See How much text fits in one batch.
Errors
Concurrency and queued-character rejections both return HTTP 429 with gRPC status code 8 (RESOURCE_EXHAUSTED).
Too many jobs already running:
{
"code": 8,
"message": "Concurrent async synthesis jobs limit of 5 exceeded",
"details": []
}Batch jobs return the same code with batch synthesis jobs in place of async synthesis jobs.
Too many characters already queued (batch only):
{
"code": 8,
"message": "Queued characters limit of 1000000 exceeded for batch jobs (987500 queued); retry after in-flight jobs complete.",
"details": []
}A job larger than your plan allows is a different failure. Retrying will not make it succeed. The response is HTTP 400 with code 3 (INVALID_ARGUMENT):
{
"code": 3,
"message": "the request's 25000 characters exceed the current plan's limit of 10000 characters per job; upgrade the plan for larger jobs",
"details": []
}Branch on code, not on message text. These messages are written for people reading a log and may be reworded at any time.
Working within the limits
Track the operation name of every job in flight. Submit new work as earlier jobs complete to keep work flowing within your concurrency limit. If you lose track, both APIs can list outstanding operations. See Listing your jobs.
Because a rejected submit creates nothing, retrying is safe and will not produce duplicate audio. Retry with exponential backoff and jitter rather than in a tight loop.
If your workload needs limits beyond what a standard plan offers, reach out to our team.