conductor-oss/conductor · error · IOException

Speech API returned empty body

Error message

Speech API returned empty body

What it means

Thrown by OpenAISpeechApi.createSpeech when the TTS request succeeds (2xx) but OkHttp's response.body() is null. This is a defensive guard rather than a normal OpenAI behavior, since a successful speech response always carries audio bytes.

Source

Thrown at ai/src/main/java/org/conductoross/conductor/ai/providers/openai/api/OpenAISpeechApi.java:86

        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

  1. Inspect any OkHttp interceptors configured on the shared httpClient for premature body consumption.
  2. Bypass intermediate proxies and call OpenAI directly to confirm the body is non-empty.
  3. If a gateway is required, ensure it streams the binary audio body through unchanged.
Defensive patterns

Strategy: try-catch

Try / catch

try {
    byte[] audio = speechApi.createSpeech(req);
} catch (IOException e) {
    if (e.getMessage().contains("empty body")) {
        // likely a proxy issue; retry once against the direct endpoint
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: An intermediary (reverse proxy, WAF, corporate gateway, or load balancer) returns a 200 with no entity body, or OkHttp is configured with a response interceptor that consumes/nulls the body before createSpeech reads it.

Common situations: A misconfigured gateway rewriting the response, a buggy OkHttp interceptor calling body().string() earlier in the chain, or a transparent caching proxy serving an empty 200.

Related errors


AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14). Data as JSON: /api/errors/5d9664609ecfa801. Report an issue: GitHub.