【Bug已解决】Claude API error despite showing that its working in Anthropic Console 解决方案一、现象长什么样你在 Anthropic Console 网页里用同一个 API key 调用 Claude一切正常但一落到代码里用anthropicPython/TS SDK或裸requests就报错401 Unauthorized/AuthenticationError: invalid x-api-key400 Bad Request伴随missing required header之类偶尔404或invalid model最迷惑的是Console 能跑、key 也是同一个curl 却失败有时本地能跑、部署到服务器就 401但 key 明明复制过去了还有人用代理式封装把请求转到别的 endpoint后报错但那属于另一类问题。一句话API 调用在代码里失败、Console 里却正常几乎总是请求构造 / 鉴权头 / key 形态与 Console 不一致而不是 key 本身无效。二、背景Anthropic API 的鉴权与请求格式有固定约定请求头必须是x-api-key: 你的key同时anthropic-version: 2023-06-01旧版也接受Authorization: Bearer key但 SDK 默认走x-api-key默认 endpoint 是https://api.anthropic.com/v1/messagesbody 必填model、max_tokens、messagesConsole 网页在后台帮你填好了这些头、版本、endpoint 和正确的 key所以在 Console 能跑不代表你的代码也构造对了。代码里失败的常见元凶key 末尾带了换行/空格、用了Authorization: Bearer但服务端期望x-api-key、把anthropic-version头漏了、或model名写错如claude-3-5-sonnet旧别名 vs 新版claude-3-5-sonnet-latest。三、根因根因是代码侧请求与 Console 生成的请求在鉴权/头上不一致# 错误示范 1key 带了换行 import os api_key os.environ[ANTHROPIC_API_KEY] \n # 复制粘贴带入的换行 # 错误示范 2只设了 Authorization没设 x-api-key / anthropic-version requests.post( https://api.anthropic.com/v1/messages, headers{Authorization: fBearer {api_key}}, # 老习惯可能不被接受 json{...}, ) # 错误示范 3漏掉 anthropic-version requests.post(..., headers{x-api-key: api_key}, json{...}) # 缺 version 头更隐蔽的是环境变量在不同 shell/部署里取值不一致本地.env有 key、服务器没注入于是服务器用空 key 请求401。但因为没有显式报错key 为空看起来就像key 失效。四、最小可运行复现import os import requests def call_claude_raw(api_key: str, model: str claude-3-5-sonnet-latest) - dict: # 正确的最小请求构造 resp requests.post( https://api.anthropic.com/v1/messages, headers{ x-api-key: api_key.strip(), # 注意 strip 掉换行/空格 anthropic-version: 2023-06-01, content-type: application/json, }, json{ model: model, max_tokens: 256, messages: [{role: user, content: hi}], }, timeout30, ) return resp.status_code, resp.text def main(): key os.environ.get(ANTHROPIC_API_KEY, ) # 模拟复制粘贴带入换行导致 401 bad_key key \n print(with newline:, call_claude_raw(bad_key)[:1]) # 很可能 401 print(stripped :, call_claude_raw(key)[:1]) # 正常 if __name__ __main__: main()运行可复现key 带换行 - 401strip 后正常。五、解决方案第一层最小直接修复最小修复是确保请求头与 key 形态和 Console 一致import os from anthropic import Anthropic # 用官方 SDK 最省心它自动加 x-api-key / anthropic-version client Anthropic(api_keyos.environ[ANTHROPIC_API_KEY].strip()) msg client.messages.create( modelclaude-3-5-sonnet-latest, max_tokens256, messages[{role: user, content: hi}], ) print(msg.content)若用裸requests务必headers { x-api-key: api_key.strip(), anthropic-version: 2023-06-01, content-type: application/json, }并确认api_key没有被换行/空格污染从环境变量读取后strip()。六、解决方案第二层结构化改进把API 调用前置校验抽成策略统一处理 key 清洗、头构造、endpoint 选择from dataclasses import dataclass, field from typing import Dict, Optional dataclass(frozenTrue) class ClaudeApiConsoleMismatchPolicy: Claude API 调用策略统一鉴权头与 key 清洗消除 Console 能跑代码不能。 规则 - api_key 自动 strip 换行/空格 - 默认用 x-api-key anthropic-version与 Console 一致 - endpoint / model 给出默认值避免漏填 endpoint: str https://api.anthropic.com/v1/messages anthropic_version: str 2023-06-01 default_model: str claude-3-5-sonnet-latest def clean_key(self, raw: str) - str: if not raw: raise ValueError(API key 为空请检查环境变量是否注入) return raw.strip() def headers(self, api_key: str) - Dict[str, str]: return { x-api-key: self.clean_key(api_key), anthropic-version: self.anthropic_version, content-type: application/json, } def build_body(self, prompt: str, *, model: Optional[str] None, max_tokens: int 256) - Dict: return { model: model or self.default_model, max_tokens: max_tokens, messages: [{role: user, content: prompt}], } def demo() - None: policy ClaudeApiConsoleMismatchPolicy() print(policy.headers(sk-ant-xxx\n)) # key 被清洗 print(policy.build_body(hi)) if __name__ __main__: demo()这样无论本地还是服务器只要 key 注入正确请求构造完全一致不会再出现Console 能、代码不能。七、解决方案第三层断言 / CI 守护import os import pytest from your_module import ClaudeApiConsoleMismatchPolicy def test_key_stripped(): policy ClaudeApiConsoleMismatchPolicy() assert policy.clean_key( sk-x \n) sk-x def test_empty_key_rejected(): policy ClaudeApiConsoleMismatchPolicy() with pytest.raises(ValueError): policy.clean_key() def test_headers_well_formed(): policy ClaudeApiConsoleMismatchPolicy() h policy.headers(sk-x) assert h[x-api-key] sk-x assert h[anthropic-version] 2023-06-01 assert content-type in h def test_body_has_required_fields(): policy ClaudeApiConsoleMismatchPolicy() body policy.build_body(hi, modelclaude-3-5-sonnet-latest) assert body[model] and body[max_tokens] and body[messages]CI 里再加一条从 secret 注入 key 后跑一次真实messages.create用最小max_tokens确保环境配置正确。八、排查清单报的是 401 还是 400401 多半是 key/头400 多半是 body/模型名。api_key末尾是否带了换行或空格读取后strip()一下。请求头是否同时有x-api-key和anthropic-versionConsole 默认如此。model名是否正确新版用claude-3-5-sonnet-latest部署环境与本地是否用了同一个 key服务器可能没注入环境变量。是否混用了Authorization: Bearer与x-api-key统一用 SDK 最稳。endpoint 是否指向api.anthropic.com/v1/messages九、小结Console 能跑、代码报错几乎从不是 key 失效而是代码侧请求与 Console 生成的请求在鉴权头/key 形态上不一致key 带换行、x-api-key头缺失、anthropic-version漏设、或model名写错。最小修复是用官方 SDK自动补齐头并对 keystrip()结构化做法是抽成ClaudeApiConsoleMismatchPolicy统一清洗与头构造最后用 pytest 守护key 必清洗、头必齐全、body 必含必填字段杜绝同类不一致。