conductor-oss/conductor · error · IOException

Chat Completions API failed with status %d: %s

Error message

Chat Completions API failed with status %d: %s

What it means

OpenAIChatCompletionsApi.createChatCompletion() throws IOException with message "Chat Completions 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 used by OpenAI-compatible providers (Perplexity, Grok/xAI, Together). Before throwing, it retries once without temperature if the error is a 400 mentioning 'temperature' (o-series quirk).

Source

Thrown at ai/src/main/java/org/conductoross/conductor/ai/providers/openai/api/OpenAIChatCompletionsApi.java:88

        Request httpRequest =
                new Request.Builder()
                        .url(baseUrl + completionsPath)
                        .header("Authorization", "Bearer " + apiKey)
                        .header("Content-Type", "application/json")
                        .post(RequestBody.create(jsonBody, JSON))
                        .build();

        try (Response response = httpClient.newCall(httpRequest).execute()) {
            ResponseBody body = response.body();
            String responseBody = body != null ? body.string() : "";
            if (!response.isSuccessful()) {
                // Some models (e.g. o-series via compatible endpoints) reject temperature.
                if (response.code() == 400
                        && responseBody.contains("temperature")
                        && request.temperature() != null) {
                    return createChatCompletion(request.withoutTemperature());
                }
                throw new IOException(
                        "Chat Completions API failed with status %d: %s"
                                .formatted(response.code(), responseBody));
            }
            log.debug("Chat Completions API response: {}", responseBody);
            return objectMapper.readValue(responseBody, ChatCompletionResult.class);
        }
    }

    // -- Request DTOs --

    @JsonInclude(JsonInclude.Include.NON_NULL)
    public record ChatCompletionRequest(
            String model,
            List<MessageItem> messages,
            Double temperature,
            @JsonProperty("top_p") Double topP,
            @JsonProperty("max_tokens") Integer maxTokens,
            List<String> stop,

View on GitHub (pinned to cf7c3e4a8a)

Solutions

  1. Read the status code and response body from the IOException message — they are included directly.
  2. For 401: verify the API key is correct for this provider (not an OpenAI key used against Perplexity/xAI).
  3. For 429: implement exponential backoff and retry; check the provider's rate-limit tier.
  4. For 404: verify baseURL and model name; ensure the path is /chat/completions (the default completionsPath).
  5. For 400 mentioning temperature: the code already retries once without it — if it still fails, check other parameters (top_p, stop, max_tokens for the specific model).

Example fix

// before: OpenAI key used against xAI
new OpenAIChatCompletionsApi(client, openaiKey, "https://api.x.ai/v1")
// after: xAI key
new OpenAIChatCompletionsApi(client, xaiKey, "https://api.x.ai/v1")
Defensive patterns

Strategy: retry

Validate before calling

// Validate request before calling the API
ChatCompletionRequest request = /* ... */;
if (request.model() == null || request.model().isBlank()) {
    throw new IllegalArgumentException("Model is required");
}
if (request.messages() == null || request.messages().isEmpty()) {
    throw new IllegalArgumentException("At least one message is required");
}
if (apiKey == null || apiKey.isBlank()) {
    throw new IllegalArgumentException("API key is required");
}

Type guard

null

Try / catch

int maxRetries = 3;
for (int attempt = 0; attempt <= maxRetries; attempt++) {
    try {
        return api.createChatCompletion(request);
    } catch (IOException e) {
        String msg = e.getMessage();
        if (msg.contains("429") || msg.contains("500") || msg.contains("503")) {
            if (attempt < maxRetries) {
                long delay = (long) Math.pow(2, attempt) * 1000;
                Thread.sleep(delay);
                continue;
            }
        }
        throw new RuntimeException("Chat Completions failed after retries", e);
    }
}

Prevention

When it happens

Trigger: POST {baseUrl}/chat/completions returns a non-2xx status. Common: 401 (invalid API key), 429 (rate limit), 404 (wrong baseURL or model), 400 (invalid parameters, unsupported model), 500/502/503 (server error), 529 (overloaded). The response body typically contains a JSON error object.

Common situations: Wrong API key for the compatible provider; baseURL missing /v1 suffix or pointing to wrong host; model name not offered by the provider; rate limit under load; provider outage (5xx); content filter rejection.

Related errors


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