API reference — Text-to-Speech

View as Markdown

The contract for invoking a self-hosted Bulbul v3 endpoint. Send model: bulbul:v3 in every request — it is the only accepted value (bulbul:v3-beta and v2 are rejected). Request bodies are JSON; responses are base64 audio (real-time) or a raw audio byte stream (streaming).

Real-time — InvokeEndpoint

FieldValue
Operationsagemaker-runtime:InvokeEndpoint
Content-Typeapplication/json
Acceptapplication/json
Max text2500 characters (use streaming for longer)

Request fields

FieldTypeRequiredDescription
textstringyesText to synthesize (≤ 2500 chars real-time).
modelstringyesbulbul:v3 — the only accepted value.
speakerstringnoVoice; default shubh. 38 voices (aditya, ritu, priya, neha, amit, kavya, …).
language_codestringnoe.g. en-IN (default).
output_audio_codecstringnowav (default), mp3, linear16, mulaw, alaw, opus, aac, flac.
speech_sample_rateintno8000, 16000, 22050, 24000 (native 24 kHz; higher is rejected 400, not upsampled).
pacefloatno0.52.0 (default 1.0).
output_audio_bitratestringnoFor lossy codecs, e.g. 128k.
enable_preprocessingboolnoText normalization.

The 38 voices are: aditya, ritu, priya, neha, rahul, pooja, rohan, simran, kavya, amit, dev, ishita, shreya, ratan, varun, manan, sumit, roopa, kabir, aayan, shubh, ashutosh, advait, anand, tanya, tarun, sunny, mani, gokul, vijay, shruti, suhani, mohit, kavitha, rehan, soham, rupali, niharika.

Response

1{ "request_id": "20260730_…", "audios": ["<base64 audio>"] }

audios always contains exactly one element (the list is a historical artifact).

Server-side streaming (SSE) — InvokeEndpointWithResponseStream

Call the streaming operation and set "stream": true in the request body. The flag lives in the JSON payload, not in an API parameter — SageMaker relays the body to the container verbatim, so calling the streaming API without the flag returns a single buffered chunk. The response is a sequence of PayloadPart events carrying raw encoded audio; concatenate them. Time-to-first-byte is ~0.5–0.6 s regardless of text length. Max text 3500 characters.

1resp = runtime.invoke_endpoint_with_response_stream(
2 EndpointName=endpoint,
3 ContentType="application/json",
4 Body=json.dumps({**payload, "output_audio_codec": "mp3", "stream": True}),
5)
6for event in resp["Body"]:
7 part = event.get("PayloadPart")
8 if part:
9 audio_out.write(part["Bytes"])

Bidirectional — InvokeEndpointWithBidirectionalStream

A full-duplex session over SigV4 HTTP/2, port 8443 (client aws-sdk-sagemaker-runtime-http2). Put model in the URL-encoded ModelQueryString. Frames are sent with data_type="UTF8":

  • Send: {"type":"config","data":{…speaker, language_code, output_audio_codec, speech_sample_rate, pace…}}, then per turn {"type":"text","data":{"text":"…"}}{"type":"flush"}; {"type":"ping"} keep-alive.
  • Receive: {"type":"audio","data":{"audio":"<base64>"}}, {"type":"event","data":{"event_type":"final"}}, and on failure {"type":"error","data":{"message":"…","code":<http status>}}.

Codec caveat. For lossy codecs (mp3 / aac / opus) the final partial frame plus the container trailer are flushed at connection close, not at the turn’s final event (~72 ms mp3 / ~128 ms aac / ~200 ms opus). WAV and LINEAR16 are byte-exact at every final. flac is accepted on real-time (200) but rejected (400) on the streaming paths.

Errors

See Error handling on SageMaker for the 424 collapse and the shared envelope. Bad input → 400 invalid_request_error (wrong model, empty or over-length text, unsupported speech_sample_rate, pace out of range, malformed JSON) with a descriptive Pydantic message:

1{
2 "error": {
3 "message": "Validation Error(s):\n- model: Input should be 'bulbul:v3'",
4 "code": "invalid_request_error",
5 "request_id": "20260729_…"
6 }
7}

Overload → 503 / code: "service_overloaded" with body retry_after (shed immediately, never queued):

1{
2 "error": {
3 "message": "server at capacity, retry later",
4 "code": "service_overloaded",
5 "request_id": "20260729_…",
6 "retry_after": 5
7 }
8}