龙虾AGI通用实验室Lobster AGI Lab · est. 20262026 · 09 · 16
首页 / 文章 / AI持续学习
AI持续学习

如何把 Claude 的会员账号变成了 API使用?

格里灰丝2026-04-12约 8938 字Markdown 原文

故事开始于一个让我睡不着的小别扭。

我是 Claude 的订阅用户,我可以在浏览器里跟它聊一整天,也可以用 Claude Code 让它帮我改代码。这一切都包在订阅里,用得很爽。

直到有一天我想做个小工具,需要在我自己的 Python 脚本里调用 Claude API。Anthropic 说:可以,但你得另外买 API 额度,按 token 收费。

我愣了一下。

凭什么?同一个我、同一个 Claude、同一个模型,只是把"在浏览器里说话"换成"在代码里说话",就要再付一次钱?这两件事的成本差异在哪里?Anthropic 后端的 GPU 难道还能区分"哦这次请求来自浏览器,免费;那次来自 Python,收费"?

带着这个疑问,我打开了 Claude Code 的源码,以及 Anthropic 公开的 API 文档。然后我看到了一件几乎所有人都没意识到的事。

二 · 一扇门,两张卡

打开浏览器跟 Claude 聊天的时候,你的浏览器在背后做什么?它在向 https://api.anthropic.com/v1/messages 发 POST 请求。

打开 Claude Code 在终端里写代码的时候,Claude Code 在背后做什么?它也在向 https://api.anthropic.com/v1/messages 发 POST 请求。

你买了 API 额度,用 Python SDK 调用 Claude 的时候,SDK 在背后做什么?它还是在向 https://api.anthropic.com/v1/messages 发 POST 请求。

URL 一样、请求方法一样、请求体的 JSON 结构一样、返回的 SSE 流格式一样。Anthropic 后端那台真正跑模型的服务器,根本不知道、也不在乎调它的人是浏览器、CLI 还是 Python 脚本——对它来说,都是一坨 HTTP。

那这三种调用,在 HTTP 层唯一的差别是什么?

认证头。

订阅用户(浏览器、Claude Code)用的是 OAuth 2.0 流程换来的 access token,放在 Authorization: Bearer 里。 API 用户用的是花钱买的 key,放在 x-api-key: 里。

就这一个字段的差别。

到这里,那个让我睡不着的小别扭就有答案了:订阅和 API 不是两个产品,是同一个产品的两种计费方式。门是同一扇,只是 Anthropic 在门口放了两台刷卡机,一台按月扣订阅,一台按 token 扣余额。

那我能不能拿着我的会员卡,去刷 API 那一台?

三 · 拿到那张会员卡

OAuth 这一步比想象的简单。Claude Code 的 client_id 是公开的(9d1c250a-e61b-44d9-88ed-5944d1962f5e),scope 是 org:create_api_key user:profile user:inference。任何人都可以用这个 client_id 走标准 OAuth 流程。

具体来说:本地起一个 HTTP server 监听回调端口,生成 PKCE 的 code_verifier 和 code_challenge,把用户浏览器送到 https://claude.ai/oauth/authorize?...&code_challenge=...。用户点授权,浏览器带着 code 跳回本地端口。再用 code 和 code_verifier 去 POST /v1/oauth/token,换出来一组东西:

{  "access_token": "eyJhbG...",  "refresh_token": "rt_xxx...",  "expires_in": 28800,  "account": { "email": "...", "uuid": "..." }}

这个 access token 就是浏览器和 Claude Code 自己用来调 API 的同一种 token。8 小时过期,过期前用 refresh_token 续。

然后是激动人心的一刻:我把这个 token 塞进一个 Authorization: Bearer 头,向 /v1/messages 发了一个最简单的请求,带上 anthropic-beta: oauth-2025-04-20(OAuth token 必须带这个 beta 标记)。

服务器返回 200。Claude 回了我一句话。它真的进去了。

我以为故事到这里就结束了。

四 · 然后我被一眼识破

接下来我做了一件正常人都会做的事:我把 system prompt 改成了我自己想要的内容,带了点 temperature,加了点 tools。重发。

400 Bad Request

我换了一个简单的请求重试。400

我把请求体一字不差地照抄 Anthropic 官方文档的示例。400

我开始意识到,这扇门后面不是只有一个刷卡机。门后面还坐着一个门卫,他不光看你的卡,他还看你这个人。

接下来三天,我一层一层把这个门卫看的东西摸清楚了。一共有四层。

第一层:beta 头。OAuth token 调用必须带 anthropic-beta: oauth-2025-04-20。少了直接 401。这一层最好过。

第二层:request body 的形状。OAuth 路径下,服务端对请求体有几个隐藏约束。temperature 字段不能传(传了直接 400)。tool_choice 在某些组合下也不能传。max_tokens 有上限。system 字段必须存在,而且必须以一段特定的字符串开头——这段字符串不是随便写的,是 "You are Claude Code, Anthropic's official CLI for Claude."。一个字符都不能差。如果你想用自己的 system prompt,你得把它放进 system 数组的第二段,第一段必须留给这句官方话。

读到这儿你可能已经感觉到味道不对了。一个公开的 API,为什么会有"你必须自称是 Claude Code"这种约束?因为这个 API 端点压根不是设计给"公开"用的。它是设计给 Anthropic 自己的客户端用的。OAuth token 这条路,在 Anthropic 的设计里,只服务于一种客户端:Claude Code 本身

第三层:HTTP 头的"客户端身份"。光在 body 里自称 Claude Code 不够,你的 HTTP 头也得长得像 Claude Code。Claude Code 用的是 Stainless 这个 SDK 生成器,它会带一组特征头:x-stainless-lang: jsx-stainless-package-versionx-stainless-runtime: nodex-stainless-runtime-versionx-stainless-osx-stainless-arch,以及一个形如 claude-cli/2.x.x 的 user-agent。这些头服务端会读,会校验,会和 body 里的"我是 Claude Code"这句话做交叉验证。版本号对不上,或者某个字段缺失,服务端的反应从"功能降级"到"直接拒绝"都有。

到这里我以为已经摸到底了。我把这些头全部填上,请求又能通了。一切看起来正常。

直到我注意到一件诡异的事:我每次发出去的 system prompt,在到达服务端之前,被悄悄塞了点东西进去

五 · 那串只有真 Claude Code 才会算的暗码

我抓了一份真实的 Claude Code 的请求 dump,跟我自己构造的请求做了一次 diff。它们的 system 字段几乎一样,但真 Claude Code 的版本里,在那段固定的 "You are Claude Code..." 后面,多了一小段看起来像随机字符的东西。形如:

<billing-metadata version="..." fingerprint="a3f8b2..."/>

这就是 Claude Code 的 billing fingerprint。

它的逻辑大概是这样:Claude Code 在每次发请求之前,在客户端本地,用一个藏在它自己代码里的算法,从这次对话的 messages 数组里取出几个特定位置的字符(比如第 4、第 7、第 20 个),拼上一个固定的盐值(SALT),拼上 Claude Code 自己的版本号,做一次 SHA256,取一段哈希结果作为 fingerprint。这段 fingerprint 跟着 system prompt 一起发到服务端,服务端用同样的算法算一遍,对得上才放行。

你看出这件事的精妙了吗?

它不是一个静态的密钥。它是一个和这次请求内容绑定的动态校验码。你不能简单地"抓一个 fingerprint 然后到处复用"——换一段 messages,fingerprint 就变了。你必须复刻整个算法,才能对每一次请求都生成正确的 fingerprint。

而这个算法,藏在 Claude Code 的 JavaScript 二进制包里,经过了混淆。

但混淆挡不住决心。开源社区里有人把这个 bundle 一段一段反混淆,把那个 SALT 字符串、那几个消息位的索引、SHA256 的具体调用方式,全部挖了出来。挖出来之后,用三十行不到的代码就能复现。从那以后,任何想伪装成 Claude Code 的程序,都可以本地算出正确的 fingerprint。

到这一步,我手上的"伪装 Claude Code"已经能完整工作了。会员卡刷了,暗号对了,衣服穿对了,自报家门也对了,连那段动态校验码都算对了。门卫每次都放行。

我以为这就是故事的终点。

然后我看了看开源社区,发现有人沿着完全不同的路,在做同一件事。

六 · 两条路,两种信仰

我把社区里两类有代表性的开源项目都翻了一遍代码。它们都在做"把订阅变成 API"这件事,但它们的设计哲学几乎是镜像反着的

第一类项目,我把它叫"协议派"。

它的形态是一个轻量的 TypeScript 单进程,一两千行代码,没有数据库,token 存在本地一个 JSON 文件里。一次只服务一个 OAuth 账号。它做的所有事情,都集中在让一次请求看起来完美这件事上:

完整复刻 Claude Code 的 OAuth 流程,包括 PKCE

完整模拟 Stainless SDK 的所有 HTTP 头,包括随机化 OS/Arch 组合

实现了 billing fingerprint 算法

给每个下游 API key 分配独立的 session id,并在 30-300 分钟之间随机轮换,模拟"不同的 Claude Code 实例在分别使用"

device_id 持久化在磁盘,跨重启不变,模拟一台真实安装的机器

token 距离过期 4 小时时自动 refresh,每 60 秒一次健康检查

一个 OpenAI 兼容的 /v1/chat/completions 翻译层,把 OpenAI 格式的 request 翻译成 Anthropic 格式,把 Anthropic 的 SSE 流翻译回 OpenAI 的 SSE chunk

它的核心代码,有相当大一块都在做"翻译"和"伪装"。它赌的是:如果你能让每一次请求都和真 Claude Code 不可区分,Anthropic 在请求层面就抓不到你

第二类项目,我把它叫"调度派"。

它的形态完全不同。Go 后端 + Vue 前端 + PostgreSQL + Redis,完整的多用户、多账号、多平台网关。一次服务的不是一个账号,是一个账号池——上游可以接几十个 OAuth 账号、几十个 API key,下游可以分发给几百个用户,每个用户有自己的 quota 和 rate limit。

它的核心代码花在了完全不一样的地方:

账号选择:每次请求进来,根据 SHA256(client_ip + user_agent + api_key_id) 算出一个粘性 hash,把同一个下游用户始终路由到同一个上游账号(避免 session 跳来跳去触发风控),hash 在 Redis 里缓存一小时

负载感知:在粘性的基础上,排除已经被限速的账号、排除并发已经打满的账号、按当前并发数选最低负载的

模型路由:不同账号支持的模型不同,按请求的 model 字段过滤可用账号

故障转移:一个账号失败,立刻退出当前账号,在剩下的池子里重选,最多重试 10 次

计费:每次请求结束后,从 SSE 流里抽出 token usage,异步推到一个 worker 池,worker 算费用、写 PostgreSQL、扣 Redis 里的余额缓存

并发控制:用户级和账号级两层信号量,防止单个用户打爆账号,也防止单个账号被全部用户压垮

传输层:用 uTLS 库定制 JA3/JA4 指纹,绕开 Cloudflare 这类前端的 TLS 指纹检测;HTTP 客户端走代理池,每个账号绑一个出口 IP

而它的"伪装"部分,反而很薄。它会在请求里注入 Claude Code 的 system prompt,会带上必要的 beta 头和基本的 SDK 头,但它没有完整复刻 billing fingerprint 算法(至少在我读的那个版本里没有),也没有 device_id 持久化、session 轮换这些细节。

它赌的是另一件事:就算单次请求有点小破绽,只要账号足够多、出口 IP 足够散、调度足够智能,Anthropic 没办法把这些请求关联到同一个"虚假的真实用户"上

把这两条路并排放在一起看,你就能看出它们各自在赌什么了:

**协议派赌的是:门卫不会去翻台账,只看每次刷卡的瞬间。**

**调度派赌的是:门卫就算翻台账,也分不清这是一个人在用还是一群人在用——账号摊得足够散,统计上就稀释了异常。**

两条路都没错。两条路都不完整。

七 · 那如果把它们拼在一起呢

到这里我已经在脑子里搭一个东西了。一个把两条路的优势焊在一起的"终极版"。

底座用调度派的架构。Go + PostgreSQL + Redis 这套东西,它在并发、状态管理、可扩展性上,比单进程 TypeScript 强一个数量级。账号池、粘性会话、负载感知、计费、用户管理这些,直接复用。底座解决的是"工程问题"。

伪装层用协议派的精度。把协议派的 cloaking 模块——billing fingerprint 算法、完整 SDK 头集合、system prompt 注入策略——用 Go 重写,塞进调度派的请求出口。每个请求出网之前,都过这一层。

但光是简单的 1+1 还不够。两边各自的弱点,合起来之后还在。真要让它"接近完美",至少还要再加四件事:

第一,每个上游账号绑定一个独立的"虚拟客户端身份"。 协议派的 device_id 是整个进程共享的,所有下游用户走的是同一个 device_id——这本身就是一个统计异常(一台设备上居然有几十种不同的对话风格)。改掉:每个 OAuth 账号入库时,生成一组独立且固定的客户端身份——独立的 device_id、独立的 SDK version、独立的 Node version、独立的 OS/Arch。这一组身份和这个账号绑死,跨重启不变。从服务端看,这个账号永远是"同一台机器上的同一个 Claude Code 实例"在用。

第二,版本号要"活"。 协议派的 Stainless SDK 版本号是硬编码的。Anthropic 那边的 Claude Code 真版本几个月就升一次,硬编码的版本号会越来越像一个"几个月没更新的老用户",久了本身就是异常信号。改掉:写一个后台任务定期去 GitHub releases 抓 Claude Code 的最新版本号和对应的 SDK 版本号,把账号身份里的版本号定期"升级",升级节奏接近真实用户的更新节奏。

第三,TLS 指纹要和声称的运行时一致。 调度派用了 uTLS 自定义 JA3,但它定的是一个"通用浏览器指纹"。这其实和 Claude Code 自报的 x-stainless-runtime: node 是矛盾的——Node.js 的 TLS 指纹和浏览器完全不一样。真要严丝合缝,JA3 应该是 Node.js 在那个版本上的真实 TLS 行为指纹,而且要和 HTTP 头里声称的 Node 版本对应。改掉:维护一组"Node.js 版本 → TLS 指纹"的映射,每个虚拟客户端身份用对应的指纹。

第四,请求时序要模拟人类节奏。 这一层是协议派和调度派都没做的事,但它是最关键的一层。后面会讲为什么。简单说:在网关出口加一个"行为模拟器",根据一个虚拟用户的"作息曲线",在请求之间引入延迟,让账号的请求分布不是 24 小时均匀的,而是接近真人的钟形曲线。打字快慢、阅读时间、作息周期,都建模进去。

你看,这套东西如果真的写出来,它在每一个可见的维度上,都和真实的 Claude Code 用户不可区分。HTTP 层不可区分,TLS 层不可区分,协议层不可区分,版本演化不可区分,作息分布不可区分。

我设计完这套架构,自己也兴奋了一下。然后我坐下来,打算证明它一定能成。

结果我反而证明了它一定会败。

八 · 一堵我没法绕过的墙

证明的起点是一个很朴素的问题。

我刚刚列的那四件事,都是在解决"如何让我的请求看起来像一个真实 Claude Code 用户"。但我突然意识到,这句话里有一个被我忽略的词。

一个真实用户。

我的网关同时服务着几十个下游用户。这几十个下游用户,通过粘性 hash,被分配到一个上游 OAuth 账号上。也就是说,从 Anthropic 服务端看,这一个账号上,聚合了几十个完全独立的人的对话。

哪怕我把作息曲线、打字节奏全模拟好了,有一件事我没办法模拟:这几十个独立的人,每个人都有自己的兴趣、自己的话题、自己的语言习惯、自己的项目背景。一个真人用 Claude,他这周在写 React 前端,下周可能在写 Python 爬虫,但他不会同时在讨论 Kubernetes 调优、研究英文古诗的格律、调试 CUDA 内核、用日语翻译一份法律文件、问明天北京的天气。他不会。一个真人在一段时间窗口内,他的对话内容有"主题的连续性"和"风格的一致性"——这是一个人之所以是一个人。

但我那几十个下游用户,每个人都在做完全不同的事。叠在一起,这个账号的对话内容,是一团没有连续性、横跨各种领域、各种语言、各种风格的乱炖。

这件事我没办法修。我没办法让几十个真实人类的对话,看起来像一个人产生的——除非我去过滤、改写他们的请求,而那等于我没有在做一个"代理"。

到这里,我撞上了那堵墙。

这堵墙是一个信息论意义上的事实。先说结论,再说为什么:

**几个独立的人产生的信号,叠加在一起,在统计学意义上"比一个人更复杂"。**

**而这种复杂度的差异,无法通过任何客户端伪装抹平。**

这件事的底层原理叫熵。熵的直觉很简单:它衡量的是一个信号有多"难以预测"。一个人的对话流是相对容易预测的——你知道他的偏好,你知道他这段时间在做什么,他下一句话的可能空间是被限定的。两个人的对话流叠在一起,你既要在 A 的可能空间里猜,又要在 B 的可能空间里猜,总的不确定性变大了。N 个人叠在一起,这种不确定性按一个不平凡的方式增长。

而 Anthropic 看到的是什么?是这个 OAuth 账号下的全部历史。它不是看一次请求,它看的是一段长时间的、累积的、有上下文的请求流。这段请求流的"统计形状"——它的话题分布、它的语言切换频率、它的时间分布、它的 token 长度分布、它的工具调用模式——这一整套形状,就是这个账号的指纹

而这个指纹,不在我的协议伪装范围内。我可以伪装 HTTP 头,我可以伪装 TLS,我可以伪装 fingerprint,我甚至可以伪装作息曲线。但我没办法把 N 个真实人类的对话,压缩成一个真实人类的对话的统计形状——因为前者的信息量天然大于后者。这是数学定律,不是工程问题。

更糟的是,Anthropic 不需要做任何复杂的 AI 检测就能利用这个。最朴素的统计检验就够了。比如:这个账号一周的请求里,被 langdetect 识别为"主语言"的语言种类是几种?真实用户的众数是 1,均值大概在 1-2 之间。代理账号呢?可能是 5、6、甚至 10。光这一个特征,设一个阈值,就能把绝大多数代理账号筛出来。这只是几十个可观测特征里最简单的一个。

所以我的"终极版"架构的命运是什么?它会让 Anthropic 抓不到单次请求的破绽,它会让 Anthropic 抓不到单台设备的破绽,但它没办法让 Anthropic 抓不到单个账号长期行为的破绽。

而且这堵墙不光对协议派有效,不光对调度派有效,也对我精心设计的"终极版"有效。它对任何形态的代理都有效。这是一个不依赖于具体技术的、信息论层面的上限。

所以那些项目现在为什么还没死?不是因为 Anthropic 抓不到它们。是因为 Anthropic 没在认真抓。它们活在一个倒计时里。这个倒计时不是按"代码精巧程度"在走,是按"Anthropic 什么时候觉得这件事值得治理"在走。

上一篇Agent之间的沟通交流,需要新协议吗?下一篇搞懂 Agent 架构,只需要想明白这 7 个问题