conductor-oss/conductor · error · IOException
Speech API failed with status %d: %s
Error message
Speech API failed with status %d: %s
What it means
Thrown by OpenAISpeechApi.createSpeech when the OpenAI Text-to-Speech endpoint (POST /v1/audio/speech) returns a non-2xx HTTP status. It is an IOException whose message embeds the raw response code and the server's error body so the caller can see exactly what OpenAI rejected. The provider supports both public OpenAI and Azure OpenAI auth (api-key vs Authorization: Bearer).
Source
Thrown at ai/src/main/java/org/conductoross/conductor/ai/providers/openai/api/OpenAISpeechApi.java:80
* @param request Speech request parameters
* @return Raw audio bytes in the requested format
*/
public byte[] createSpeech(SpeechRequest request) throws IOException {
String jsonBody = objectMapper.writeValueAsString(request);
Request httpRequest =
new Request.Builder()
.url(baseUrl + "/audio/speech")
.header(authHeaderName, authHeaderValue)
.header("Content-Type", "application/json")
.post(RequestBody.create(jsonBody, JSON))
.build();
try (Response response = httpClient.newCall(httpRequest).execute()) {
if (!response.isSuccessful()) {
ResponseBody body = response.body();
String errorBody = body != null ? body.string() : "";
throw new IOException(
"Speech API failed with status %d: %s"
.formatted(response.code(), errorBody));
}
ResponseBody body = response.body();
if (body == null) {
throw new IOException("Speech API returned empty body");
}
return body.bytes();
}
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public record SpeechRequest(
String model,
String input,
String voice,
@JsonProperty("response_format") String responseFormat,
Double speed) {}View on GitHub (pinned to cf7c3e4a8a)
Solutions
- Read the full message: the %s error body usually contains OpenAI's JSON with an error.message field naming the exact problem.
- Verify the API key is valid and has TTS access; for Azure confirm azureAuth=true and the deployment baseUrl.
- Confirm the model is a valid TTS model (tts-1, tts-1-hd, gpt-4o-mini-tts) and the voice is one OpenAI exposes.
- For 429 responses, add exponential backoff / retry around createSpeech.
- Check that baseUrl ends with /v1 so the appended /audio/speech resolves correctly.
Example fix
// before
byte[] audio = speechApi.createSpeech(req); // throws bare IOException
// after
try {
byte[] audio = speechApi.createSpeech(req);
} catch (IOException e) {
if (e.getMessage().contains("status 429")) {
// back off and retry
} else {
throw e;
}
} Defensive patterns
Strategy: try-catch
Validate before calling
if (speechReq.model() == null || speechReq.model().isBlank()) {
throw new IllegalArgumentException("SpeechRequest.model must be set to a valid TTS model");
}
if (speechReq.input() == null || speechReq.input().length() > 4096) {
throw new IllegalArgumentException("input must be 1-4096 characters");
} Try / catch
try {
byte[] audio = speechApi.createSpeech(req);
} catch (IOException e) {
String msg = e.getMessage();
if (msg.contains("status 429")) {
// back off and retry
} else if (msg.contains("status 401")) {
// credentials issue - do not retry
throw e;
} else {
throw e;
}
} Prevention
- Validate the TTS model and voice names against OpenAI's docs before calling.
- Keep input text within the 4096-character limit.
- Confirm baseUrl ends with /v1 for OpenAI or the deployment path for Azure.
- Add retry-with-backoff specifically for 429 responses.
When it happens
Trigger: Calling createSpeech with an invalid or expired API key (401), exhausted quota / rate-limited (429), an unsupported model name like a non-tts model (400), a blank/oversized input text exceeding the 4096-character limit (400), or a baseUrl pointing at a path that does not serve /audio/speech (404).
Common situations: Pointing baseUrl at https://api.openai.com instead of .../v1, using azureAuth=true with a plain OpenAI key, typos in the model/voice fields, hitting OpenAI's per-minute token limits under load, or a corporate proxy returning its own 4xx HTML.
Related errors
- Speech API returned empty body
- OpenAI Video API submit failed with status %d: %s
- Gemini generateAudio failed
- Embeddings API call failed:
- Speech API call failed:
AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14).
Data as JSON: /api/errors/abb6f1552434496a.
Report an issue: GitHub.