Capabilities
Script synthesis
Generate dialogue or a monologue in one request, with combined or separate utterance audio
A script is an ordered list of utterances, each with text and a stored voice. One request can produce a conversation between speakers or a monologue with one voice. TTS-2 keeps a bounded audio history within the script to help later utterances follow earlier turns naturally. Separate requests do not share this history.
Scripts work with the unary, HTTP streaming, async, and batch APIs. This request shape does not apply to WebSocket or OpenAI-compatible synthesis. Async and batch remain preview APIs.
Build a script
Send script instead of the top-level text, voiceId, or voiceDesign. Use modelId: "inworld-tts-2". Every utterance needs its own text and voiceConfig.voiceId; inline voice design is not supported inside a script.
curl --request POST 'https://api.inworld.ai/tts/v1/voice' \
--header "Authorization: Basic $INWORLD_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"modelId": "inworld-tts-2",
"language": "en-US",
"audioConfig": {"audioEncoding": "MP3", "speakingRate": 1.0},
"timestampType": "WORD",
"outputMode": "PER_UTTERANCE",
"script": {
"voices": [
{"voiceId": "Dennis", "speakingRate": 0.9, "deliveryMode": "STABLE"}
],
"utterances": [
{"text": "Welcome to the observatory.", "voiceConfig": {"voiceId": "Dennis"}},
{"text": "The telescope is ready for tonight.", "voiceConfig": {"voiceId": "Ashley"}},
{"text": "Let us look at the stars.", "voiceConfig": {"voiceId": "Dennis", "speakingRate": 1.1}}
]
}
}'script.voices is an optional list of defaults keyed by unique voiceId. It does not declare speakers or replace voiceConfig.voiceId on each utterance. For speakingRate and deliveryMode, an utterance override takes precedence over its voice defaults, which take precedence over the request settings. In this example, Dennis's first turn uses rate 0.9, Ashley inherits 1.0, and Dennis's last turn overrides the rate to 1.1 while retaining STABLE delivery.
Scripts accept 1–100 utterances. The endpoint's text limit applies to the sum of all utterance texts, measured in UTF-16 code units: 2,000 for unary and 4,000 for HTTP streaming; async and batch use their existing job limits. Splitting text into more utterances does not increase the limit. Usage counts the text across the script once.
Audio encoding, sample rate, language, model, and timestamp type are shared request settings. Per-utterance controls are voiceId, speakingRate (0.5–1.5), and deliveryMode.
Request fields
scriptobject
Ordered speech utterances for TTS-2. Replaces the request's top-level text, voiceId, and voiceDesign. Bounded audio history carries between utterances within this request. See script synthesis for examples and output formats.
Show child attributes — script fields
utterancesobject[]required
Between 1 and 100 utterances, generated in array order. The endpoint's text limit applies to the sum of their texts, measured in UTF-16 code units.
Show child attributes — utterance fields
voiceConfigobjectrequired
Stored voice and optional delivery overrides for this utterance.
Show child attributes — voiceConfig fields
voiceIdstringrequired
Stored voice to use for this utterance, or the key for an entry in script.voices. Required even when voice defaults are supplied. Inline voice design is not supported inside scripts.
voicesobject[]
Optional defaults keyed by unique voiceId. Each utterance still requires its own voiceConfig.voiceId.
Show child attributes — voice default fields
voiceIdstringrequired
Stored voice to use for this utterance, or the key for an entry in script.voices. Required even when voice defaults are supplied. Inline voice design is not supported inside scripts.
outputModeenum
COMBINED (default) returns one recording. PER_UTTERANCE returns separate utterance recordings and requires script. Both modes retain the same generation history. Async and batch per-utterance results require ZIP packaging.
Choose the output
outputMode controls whether the service returns one combined recording or separate recordings. Omitting it selects COMBINED.
| Endpoint | COMBINED | PER_UTTERANCE |
|---|---|---|
| Unary | Top-level audioContent and optional timestampInfo | Complete clips in scriptResult.utterances[] |
| HTTP streaming | Top-level audio and timestamps on one timeline | Top-level audio and timestamps tagged with scriptResult.utteranceIndex |
| Async | Existing single-recording result | scriptResult.utterances[] in the ZIP results document |
| Batch | Existing single-recording result per item | scriptResult.utterances[] on each script item in the ZIP results document |
scriptResult is omitted for combined output. With separate recordings, timestamps are relative to the start of each utterance's audio; combined output uses one cumulative timeline. Separate output requires a script and does not also produce a combined recording.
Unary response
For PER_UTTERANCE, decode each entry's audioContent as its own complete recording. Indexes are zero-based positions in the request's script.utterances array. The following excerpt omits usage and timestamp details, and uses illustrative base64 placeholders:
{
"scriptResult": {
"utterances": [
{"utteranceIndex": 0, "audioContent": "<base64 recording 0>"},
{"utteranceIndex": 1, "audioContent": "<base64 recording 1>"},
{"utteranceIndex": 2, "audioContent": "<base64 recording 2>"}
]
}
}When requested, alignment belongs to each entry's timestampInfo. Top-level audioContent is empty; a top-level timestamp object may contain empty alignment arrays and is not the script's alignment. Read the top-level usage for the whole request.
HTTP streaming response
Send the same body to /tts/v1/voice:stream. The endpoint returns newline-delimited JSON, with each response inside result. In PER_UTTERANCE mode, audio stays in result.audioContent; the wrapper supplies the index:
{"result":{"audioContent":"<first chunk of utterance 0>","scriptResult":{"utteranceIndex":0}}}
{"result":{"audioContent":"<next chunk of utterance 0>","scriptResult":{"utteranceIndex":0}}}
{"result":{"audioContent":"<first chunk of utterance 1>","scriptResult":{"utteranceIndex":1}}}The excerpt omits timestamps and usage. Group audio and timestamp updates by index. Base64-decode each audio chunk separately before concatenating its bytes. MP3 chunks for one index form one recording; follow the audio format rules for other encodings.
Indexes arrive in order. An increment means all audio and timestamp updates for the previous utterance have finished. Successful end-of-stream completes the last utterance; an error or disconnected stream does not. There is no separate utterance-completed event. An utterance with no audio still emits its index, so do not require an audio chunk on every response. Timestamp-only updates also belong to their indicated index.
Async results
Async and batch use the same packaging rules:
| Results | Packaging |
|---|---|
Hosted, COMBINED | FILES by default; ZIP optional |
Hosted, any PER_UTTERANCE output | Explicit ZIP required |
Delivered to outputConfig.resultsUri | ZIP required and selected by default, regardless of output mode |
For /tts/v1/voice:synthesizeAsync with PER_UTTERANCE, add "outputConfig": {"packaging": "ZIP"} to the script request. See results delivery for destination requirements.
Poll the returned operation as described in async synthesis. On success, its response.resultsUri points to the ZIP and response.resultsPath names the results document inside it. The operation does not inline the utterance list.
For the three-turn example, the results document contains the following structure. Other result fields are omitted:
{
"scriptResult": {
"utterances": [
{"utteranceIndex": 0, "audioPath": "utterances/0000/audio.mp3", "timestampsPath": "utterances/0000/timestamps.json"},
{"utteranceIndex": 1, "audioPath": "utterances/0001/audio.mp3", "timestampsPath": "utterances/0001/timestamps.json"},
{"utteranceIndex": 2, "audioPath": "utterances/0002/audio.mp3", "timestampsPath": "utterances/0002/timestamps.json"}
]
}
}Paths are relative to the results document inside the archive. Follow the manifest's paths instead of constructing filenames. Each audio file is independently playable. Timestamp files contain a JSON array of alignment updates and are present only when timestamps were requested.
Batch results
Place each script request inside an item's request, and set outputConfig once at the batch root:
{
"outputConfig": {"packaging": "ZIP"},
"items": [
{
"customId": "observatory-dialogue",
"request": {
"modelId": "inworld-tts-2",
"audioConfig": {"audioEncoding": "MP3"},
"outputMode": "PER_UTTERANCE",
"script": {
"utterances": [
{"text": "Welcome to the observatory.", "voiceConfig": {"voiceId": "Dennis"}},
{"text": "The telescope is ready for tonight.", "voiceConfig": {"voiceId": "Ashley"}}
]
}
}
},
{
"customId": "closing-narration",
"request": {"modelId": "inworld-tts-2", "voiceId": "Dennis", "text": "Thank you for visiting."}
}
]
}A batch can mix scripts and ordinary single-voice requests. Hosted combined batches can use FILES; any PER_UTTERANCE item or delivery to your storage requires ZIP. Each script is generated independently; audio history is not shared between batch items.
After the batch operation completes, download response.resultsUri and open the file named by response.resultsPath inside the ZIP. Each successful script item has its own wrapper, while combined items retain their existing shape. This results excerpt corresponds to the request above:
{
"completedItems": 2,
"failedItems": 0,
"results": [
{
"customId": "observatory-dialogue",
"scriptResult": {
"utterances": [
{"utteranceIndex": 0, "audioPath": "items/0/utterances/0000/audio.mp3"},
{"utteranceIndex": 1, "audioPath": "items/0/utterances/0001/audio.mp3"}
]
}
},
{"customId": "closing-narration", "audioPath": "items/1.mp3"}
]
}With timestamps enabled on a script item, each utterance also has a timestampsPath. Inspect item errors and the success/failure counts: a completed batch operation does not mean every item succeeded. A failed script item does not return a successful prefix of its utterances.