conductor-oss/conductor · error · IOException

Responses API failed with status %d: %s

Error message

Responses API failed with status %d: %s

What it means

OpenAIResponsesApi.createResponse() throws IOException with message "Responses API failed with status %d: %s" when the HTTP response is not 2xx. The %d is the HTTP status code, %s is the raw response body. This is the low-level OkHttp client for POST {baseUrl}/responses (the newer OpenAI Responses API). Before throwing, it retries once without temperature if the error is a 400 mentioning 'temperature' (o-series models reject temperature). Auth header is configurable for Azure (api-key header) vs OpenAI (Bearer).

Source

Thrown at ai/src/main/java/org/conductoross/conductor/ai/providers/openai/api/OpenAIResponsesApi.java:101

        Request httpRequest =
                new Request.Builder()
                        .url(baseUrl + "/responses")
                        .header(authHeaderName, authHeaderValue)
                        .header("Content-Type", "application/json")
                        .post(RequestBody.create(jsonBody, JSON))
                        .build();

        try (Response response = httpClient.newCall(httpRequest).execute()) {
            String responseBody = readBody(response);
            if (!response.isSuccessful()) {
                // o-series and some newer OpenAI models reject temperature — retry without it.
                if (response.code() == 400
                        && responseBody.contains("temperature")
                        && request.temperature() != null) {
                    return createResponse(request.withoutTemperature());
                }
                throw new IOException(
                        "Responses API failed with status %d: %s"
                                .formatted(response.code(), responseBody));
            }
            log.debug("Responses API response: {}", responseBody);
            return objectMapper.readValue(responseBody, ResponseResult.class);
        }
    }

    private String readBody(Response response) throws IOException {
        ResponseBody body = response.body();
        return body != null ? body.string() : "";
    }

    // -- Request DTOs --

    /**
     * Reasoning config block on the Responses API request. OpenAI's Responses API takes a nested
     * object {@code "reasoning": {"effort": "...", "summary": "..."}} rather than the flat {@code

View on GitHub (pinned to cf7c3e4a8a)

Solutions

  1. Read the status code and body from the IOException message.
  2. For 400 model-not-supported: switch to a Responses-API-compatible model (gpt-4o, gpt-4o-mini, o1, o3, o4-mini, gpt-5 series).
  3. For 401: verify the API key; if using Azure, ensure azureAuth=true so the api-key header is used instead of Bearer.
  4. For 429: implement backoff and retry.
  5. For 400 reasoning parameter issues: verify reasoningEffort/reasoningSummary values match what the model supports (only reasoning models accept these).
  6. Verify baseURL ends with /v1 (the OpenAI provider constructor normalizes this via ensureV1, but direct OpenAIResponsesApi construction does not).

Example fix

// before: Azure OpenAI using Bearer auth (wrong)
new OpenAIResponsesApi(client, apiKey, "https://my-resource.openai.azure.com/v1", false)
// after: use azureAuth=true for api-key header
new OpenAIResponsesApi(client, apiKey, "https://my-resource.openai.azure.com/v1", true)
Defensive patterns

Strategy: retry

Validate before calling

// Validate response request before calling
OpenAIResponsesApi.ResponseRequest request = /* ... */;
if (request.model() == null || request.model().isBlank()) {
    throw new IllegalArgumentException("Model is required");
}
if (apiKey == null || apiKey.isBlank()) {
    throw new IllegalArgumentException("API key is required");
}
// Verify model is Responses-API-compatible
String model = request.model().toLowerCase();
boolean supportsResponses = model.startsWith("gpt-4o") || model.startsWith("o1")
    || model.startsWith("o3") || model.startsWith("o4") || model.startsWith("gpt-5");
if (!supportsResponses) {
    log.warn("Model '{}' may not support the Responses API", request.model());
}

Type guard

null

Try / catch

int maxRetries = 3;
for (int attempt = 0; attempt <= maxRetries; attempt++) {
    try {
        return api.createResponse(request);
    } catch (IOException e) {
        String msg = e.getMessage();
        // Retry transient errors
        if ((msg.contains("429") || msg.contains("500") || msg.contains("503")
            || msg.contains("529")) && attempt < maxRetries) {
            long delay = (long) Math.pow(2, attempt) * 1000;
            Thread.sleep(delay);
            continue;
        }
        throw e;
    }
}

Prevention

When it happens

Trigger: POST {baseUrl}/responses returns non-2xx. Common: 401 (invalid/expired API key), 429 (rate limit), 400 (model not supported by Responses API, invalid reasoning config, unsupported parameters), 404 (wrong baseURL or model), 500/502/503 (server error), 529 (overloaded).

Common situations: Using a model that only supports Chat Completions (not Responses API) like gpt-3.5-turbo; expired API key; rate limit; reasoning-model parameter conflicts (the code auto-retries without temperature but other params like topP/stop may conflict); Azure auth header misconfigured (using Bearer instead of api-key header); baseURL not ending in /v1.

Related errors


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