---
title: "Synthesize many inputs in one request"
method: POST
path: "/v1/audio/speech/batch"
tags: ["audio"]
---

# Synthesize many inputs in one request

`POST /v1/audio/speech/batch`

## Request

- Base URL: `https://api.ditto.audio` (the document declares no server; this is the origin it was published from)
- URL: `https://api.ditto.audio/v1/audio/speech/batch`
- Auth: HTTP bearer

Answers `application/x-ndjson` by default: one JSON object per line, written the moment that item finishes, each carrying its audio as base64. A final `{"object":"batch.done"}` line closes the stream. Send `stream: false` for one buffered `application/json` envelope instead.

Each item’s audio is a COMPLETE file — real sizes in its header, never the streaming placeholder — so you can write the decoded bytes straight to disk. Set `response_format` on the batch, or per item to mix formats in one request; each item carries `format`, `mimeType`, `sampleRate`, `channels` and `bitsPerSample` next to its audio.

Audio defaults to MPEG-2 Layer III (MP3), 24 kHz, mono, 128 kbps, with no Xing/LAME header and no ID3 tag — the body starts at the first audio frame. Set `response_format` for anything else: mp3, mp3_64, mp3_160, wav, wav24, wav32f, flac, flac24, pcm, pcm_f32, aiff, caf, au, mulaw, alaw. The exact numbers for a given response are echoed in the `X-Ditto-Audio-*` headers; decode and resample from those rather than assuming a rate. GET /v1/audio/format describes every encoding in full, without authentication.

Hi-fidelity formats bill at 1.2× the normal rate, multiplied by the model’s own weighting — the audio is identical, but the bytes are several times larger. `mp3` (the default), `mp3_64`, `mulaw` and `alaw` are unchanged at 1×. Each format’s exact multiplier is in GET /v1/audio/format and in the `X-Ditto-Audio-Cost-Multiplier` response header.

**Results are not in request order.** Items are reordered server-side so that work sharing a (model, voice, language) runs contiguously — the serving node holds ONE voice slot, so an interleaved order forces a speaker reload on every item, which is the cost this endpoint exists to remove. Streamed lines therefore arrive as they complete; every line carries `index` (its position in your `items` array), and the buffered envelope is sorted back into request order for you.

Failure is PER ITEM: a bad voice on one item produces a failed result for that item with the same `status`/`code` the single-shot route would have returned, and does not affect the others. The HTTP status is 200 whenever the batch itself was accepted.

Aborting the request stops dispatching further items — in-flight synthesis is cancelled at the node, so cancelling saves GPU.

Up to 64 items and 200000 characters per request. For more, or for work too long to hold a connection open, use POST /v1/audio/batches.

## Request body

- SpeechBatchDto
  - `model` 'kokoro' | 'xtts' | 'pocket': Default model for every item that doesn’t set its own.
  - `voice` string: Default voice for every item that doesn’t set its own.
  - `language` string: Default language. Omit to infer per item from its own text.
  - `speed` number
  - `response_format` 'mp3' | 'mp3_64' | 'mp3_160' | 'wav' | 'wav24' | 'wav32f' | 'flac' | 'flac24' | 'pcm' | 'pcm_f32' | 'aiff' | 'caf' | 'au' | 'mulaw' | 'alaw': Default audio encoding for every item that doesn’t set its own. Every item is a COMPLETE file — real sizes in its header, not the streaming placeholder — so the result is directly playable and directly saveable. Hi-fidelity formats bill at 1.2× the normal rate, multiplied by the model’s own weighting — the audio is identical, but the bytes are several times larger. `mp3` (the default), `mp3_64`, `mulaw` and `alaw` are unchanged at 1×. Each format’s exact multiplier is in GET /v1/audio/format and in the `X-Ditto-Audio-Cost-Multiplier` response header.
  - `pauses` PausesDto
    - `sentence` number: Milliseconds after a sentence (. ! ? …). Setting it sends every sentence as its own engine call, so EVERY sentence boundary gets exactly this pause instead of the model’s own.
    - `clause` number: Milliseconds where a sentence too long for one engine call is cut at a comma, semicolon, colon or dash. Commas inside a call are paced by the model and are not affected.
    - `paragraph` number: Milliseconds at a blank line in `input`.
  - `items` SpeechBatchItemDto[], required: Up to 64 items, answered on this one connection. For more than that (or for work you don’t want to hold a socket open for), create an async job with POST /v1/audio/batches instead.
    - `id` string: Your own correlation id, echoed back on this item’s result. Never interpreted by us and never required to be unique — results also carry `index`, which always identifies the item unambiguously.
    - `model` 'kokoro' | 'xtts' | 'pocket': Overrides the batch default for this item.
    - `voice` string: Overrides the batch default for this item.
    - `input` string, required: The text to speak. The only required field.
    - `language` string: Overrides the batch default for this item. See GET /v1/audio/languages.
    - `speed` number: Overrides the batch default for this item.
    - `response_format` 'mp3' | 'mp3_64' | 'mp3_160' | 'wav' | 'wav24' | 'wav32f' | 'flac' | 'flac24' | 'pcm' | 'pcm_f32' | 'aiff' | 'caf' | 'au' | 'mulaw' | 'alaw': Overrides the batch default for this item — each result is its own complete file, so a batch may mix formats freely. Billed at this item’s own rate.
    - `pauses` PausesDto
      - `sentence` number: Milliseconds after a sentence (. ! ? …). Setting it sends every sentence as its own engine call, so EVERY sentence boundary gets exactly this pause instead of the model’s own.
      - `clause` number: Milliseconds where a sentence too long for one engine call is cut at a comma, semicolon, colon or dash. Commas inside a call are paced by the model and are not affected.
      - `paragraph` number: Milliseconds at a blank line in `input`.
  - `stream` boolean: true (default): `application/x-ndjson` — one JSON object per line, written the moment that item finishes, so you can start using results before the batch is done. false: one buffered `application/json` envelope `{ results, summary }` with results in REQUEST order.

## Response `200`

NDJSON stream of `batch.item` lines followed by one `batch.done`, or the buffered `{ results, summary }` envelope when `stream:false`.

- object

## Other responses

- `400`: EMPTY_BATCH / BATCH_TOO_LARGE, or a malformed body.
- `401`: Missing, malformed, revoked or expired credential.
- `402`: QUOTA_EXCEEDED — the batch would exceed the monthly allowance. The whole batch is refused; nothing is synthesized.
- `403`: Key lacks the `audio:speech` scope.
- `429`: Rate limited (10/min per account). Retry after the `Retry-After` header.

---

[API](https://skmtc.dev/ditto/apis/ditto-audio-api.md) · [All operations](https://skmtc.dev/ditto/apis/ditto-audio-api/llms.txt) · [OpenAPI document](https://skmtc.dev/ditto/apis/ditto-audio-api/revisions/30c8d7c218da?raw)
