{"record":{"id":"21f158f00d12e259","repo":"conductor-oss/conductor","slug":"anthropic-messages-api-failed-with-status-d-s","errorCode":null,"errorMessage":"Anthropic Messages API failed with status %d: %s","messagePattern":"Anthropic Messages API failed with status (.+?): (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"ai/src/main/java/org/conductoross/conductor/ai/providers/anthropic/api/AnthropicMessagesApi.java","lineNumber":97,"sourceCode":"                        .header(\"anthropic-version\", anthropicVersion)\n                        .header(\"Content-Type\", \"application/json\")\n                        .post(RequestBody.create(jsonBody, JSON));\n\n        // Add beta header if request has beta features\n        if (request.betaFeatures() != null && !request.betaFeatures().isEmpty()) {\n            httpBuilder.header(\"anthropic-beta\", String.join(\",\", request.betaFeatures()));\n        }\n\n        try (Response response = httpClient.newCall(httpBuilder.build()).execute()) {\n            String responseBody = readBody(response);\n            if (!response.isSuccessful()) {\n                // Newer Anthropic models deprecate temperature — retry once without it.\n                if (response.code() == 400\n                        && responseBody.contains(\"temperature\")\n                        && request.temperature() != null) {\n                    return createMessage(request.withoutTemperature());\n                }\n                throw new IOException(\n                        \"Anthropic Messages API failed with status %d: %s\"\n                                .formatted(response.code(), responseBody));\n            }\n            log.debug(\"Anthropic Messages API response: {}\", responseBody);\n            return objectMapper.readValue(responseBody, MessagesResponse.class);\n        }\n    }\n\n    private String readBody(Response response) throws IOException {\n        ResponseBody body = response.body();\n        return body != null ? body.string() : \"\";\n    }\n\n    // -- Request DTOs --\n\n    @JsonInclude(JsonInclude.Include.NON_NULL)\n    public record MessagesRequest(\n            String model,","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/conductor-oss/conductor/blob/cf7c3e4a8adfb158be778ab1ec525323c363cd3a/ai/src/main/java/org/conductoross/conductor/ai/providers/anthropic/api/AnthropicMessagesApi.java#L79-L115","documentation":"AnthropicMessagesApi.createMessage() throws this IOException when the HTTP response status is not 2xx, after exhausting the automatic temperature-removal retry (which fires only on HTTP 400 responses whose body contains 'temperature'). The message includes the raw HTTP status code and the full response body, so the Anthropic API's own error JSON is visible. This is the single choke-point for all API-level rejections from the Anthropic Messages API.","triggerScenarios":"HTTP 401 (invalid or missing x-api-key), 403 (forbidden — region blocked or key suspended), 429 (rate limit / quota exceeded), 500 or 529 (Anthropic server-side overload), 400 with non-temperature validation errors such as an unknown model name, missing required max_tokens field, or a thinking config shape incompatible with the target model (e.g. legacy 'thinking.type=enabled' sent to Opus 4.7+).","commonSituations":"Using a model name that doesn't exist or has been deprecated. Sending a thinking configuration that the model line rejects (Opus 4.7 requires adaptive thinking; Sonnet/Haiku require enabled). API key expired or revoked. Burst traffic hitting the per-minute token rate limit. max_tokens omitted or set to 0 (Anthropic requires it).","solutions":["Read the status code (first %d) and response body (second %s) from the exception message to identify the specific API rejection.","401/403: verify the API key is valid and the account is active in the Anthropic console.","429: implement exponential backoff retry at the caller level, or reduce request frequency.","400 with model error: verify the model name exists and is accessible to your API key tier.","400 with thinking error: for Opus 4.7+ use adaptive thinking (AnthropicChatModel handles this via requiresAdaptiveThinking), for older models use enabled thinking.","Ensure max_tokens is always set — the code defaults to 8192 but a caller can override with null/0."],"exampleFix":"// before — no retry on rate limit\ntry {\n    return chatModel.call(prompt);\n} catch (RuntimeException e) {\n    throw e;\n}\n\n// after — retry on 429 with backoff\nfor (int attempt = 0; attempt < 3; attempt++) {\n    try {\n        return chatModel.call(prompt);\n    } catch (RuntimeException e) {\n        if (e.getCause() instanceof IOException io\n                && io.getMessage().contains(\"status 429\")\n                && attempt < 2) {\n            Thread.sleep((long) Math.pow(2, attempt) * 1000);\n            continue;\n        }\n        throw e;\n    }\n}","handlingStrategy":"try-catch","validationCode":"// Validate required fields before building the Anthropic request\nvoid validateRequest(MessagesRequest.Builder builder) {\n    if (builder.maxTokens == null || builder.maxTokens <= 0) {\n        builder.maxTokens(8192); // Anthropic requires max_tokens\n    }\n    if (builder.model == null || builder.model.isBlank()) {\n        throw new IllegalArgumentException(\"Model name is required\");\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    return messagesApi.createMessage(request);\n} catch (IOException e) {\n    String msg = e.getMessage();\n    if (msg.contains(\"status 401\")) {\n        throw new SecurityException(\"Invalid Anthropic API key\", e);\n    } else if (msg.contains(\"status 429\")) {\n        throw new RateLimitException(\"Anthropic rate limit exceeded\", e);\n    } else if (msg.contains(\"status 5\")) {\n        throw new ServiceUnavailableException(\"Anthropic server error: \" + msg, e);\n    }\n    throw e;\n}","preventionTips":["Always set max_tokens (the code defaults to 8192 but verify it's not overridden to null/0).","Verify the model name exists and is available to your API key tier before sending production traffic.","For Opus 4.7+: the code handles adaptive thinking automatically via requiresAdaptiveThinking() — don't bypass it.","Monitor for 429 responses and implement client-side rate limiting to stay under Anthropic's RPM/TPM limits.","Read the response body in the exception message — Anthropic's error JSON tells you exactly what to fix."],"tags":["anthropic","http-error","api-error","authentication","rate-limit"],"backgroundTag":null,"analyzedSha":"cf7c3e4a8adfb158be778ab1ec525323c363cd3a","analyzedAt":"2026-08-14T03:33:19.897Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}