网关对外暴露 OpenAI 兼容接口、给 Claude Code 用的 Anthropic Messages 接口、给 Codex 用的 Responses 接口,以及几个运维端点。OpenAI 兼容客户端把 Base URL 设成 http://<主机>:7863/v1、API Key 填 api_key 或面板里建的客户端密钥即可;Claude Code、Codex 的接法见 README 的接入客户端。
| 端点 | 鉴权 | 说明 |
|---|---|---|
POST /v1/chat/completions |
Bearer | OpenAI 兼容对话补全,支持流式与非流式、工具调用、图片输入、推理参数。网关不限制请求体大小,超限由上游返回 |
POST /v1/messages |
Bearer 或 x-api-key |
Anthropic Messages 格式(Claude Code),见下节 |
POST /v1/messages/count_tokens |
Bearer 或 x-api-key |
按字数估算输入 token,不发上游 |
POST /v1/responses |
Bearer | OpenAI Responses 格式(Codex) |
GET /v1/models |
Bearer | 模型列表,见模型名 |
GET /status |
Bearer | 账号状态汇总与每个账号的详情(积分、冷却、熔断、在途、粘性),以及按「域 + 模型」聚合的限额视图 model_locks |
GET /healthz |
无 | 健康检查:有可用账号返回 200,否则 503 |
GET /readyz |
无 | 就绪检查:平时 200,收到停止信号后 503 |
/panel/ |
面板内输入 | Web 管理面板;接口见面板接口 |
Base URL 多写了 /v1、请求成了 /v1/v1/... 时,网关不校验密钥、不打上游,直接回 400 说明怎么改(Claude Code 的路径按 Anthropic 格式,其余按 OpenAI 格式)。不回 404 是因为 Claude Code 收到 404 一律提示模型不存在,看不到原因。
鉴权规则:
/v1/*认管理员密钥(api_key)和客户端密钥,放在Authorization: Bearer <密钥>;两个/v1/messages端点在没有Authorization头时也认x-api-key: <密钥>(Claude Code 用ANTHROPIC_API_KEY时发这个头)。/status和面板只认管理员密钥。api_key为空、也没建过客户端密钥时全部放行;只要建过客户端密钥,/v1/*就必须带有效密钥,api_key为空也一样(/status和面板仍然放行)。/healthz、/readyz始终不鉴权。每个响应都带X-Request-Id。
给别人用时在面板「密钥」页建客户端密钥,不用把 api_key 发出去。每个密钥可以设:
- 启用开关和到期时间:停用、过期、删掉的密钥回 401,
error.code分别是key_disabled、key_expired、key_deleted,message说明是哪个密钥、哪天到期; - 每天和总量的用量上限:请求次数、token、积分三项,不填 = 不限。每天的上限按北京时间 00:00 重置;
- 模型白名单:只认
*通配、不区分大小写,按经模型路由改写后实际发给上游的模型名匹配,不带cn:/global:前缀。比如 Claude Code 发claude-sonnet-4-5、被路由改成glm-5.2,白名单里要有glm-5.2(或glm-*)。不在白名单里回 403model_not_allowed(Claude Code 入口是permission_error),GET /v1/models也只列白名单放行的模型。
到上限时回 429 key_quota_exceeded,带 X-Should-Retry: false(Anthropic、OpenAI 的 SDK 看到它不自动重试);每天的上限另带 Retry-After,是到北京时间 00:00 的秒数。计数口径:
- 请求次数按客户端请求计,换号重试不重复计。放行时先计上,最后失败的(没号可用、上游报错)再退还,所以只算成功的请求,号池出问题时客户端的自动重试不会把次数烧光;客户端中途断开的照算;
- token、积分在上游回复后计,上游没回积分的请求不计积分。并发请求在放行时还没计入 token 和积分,可能略超上限;
- 管理员密钥不受这些限制。
用量统计和请求记录都按密钥区分:用量里客户端密钥记它的 id、管理员密钥记 admin,没开鉴权和 1.3.0 之前的数据不区分;请求记录的 api_key 是密钥名,管理员密钥记「管理员」。密钥连同原文和计数存在数据目录的 api_keys.json(0600),用量计数每 10 秒写一次盘,停机时再写一次。
/v1/messages 和 /v1/responses 先把请求翻译成 OpenAI 对话格式,再走和 /v1/chat/completions 完全相同的流程:选号、换号重试、会话粘性、出站请求对齐、用量与请求日志都一样;上游的回复再翻译回客户端的格式,流式逐帧转换。请求日志里这两类请求的路径照实记成 /v1/messages、/v1/responses。
协议翻译直接用 CLIProxyAPI 的翻译层(sdk/translator),实际效果:
| 客户端发的 | 到上游变成 |
|---|---|
Claude Code 的 thinking(含 adaptive)与 output_config.effort |
reasoning_effort;超出模型支持范围时网关再按模型目录降级 |
Claude Code 的 cache_control、metadata.user_id、context_management、思考块 |
丢掉(上游是 OpenAI 格式,没有对应字段) |
Codex 的 store、include、prompt_cache_key、内置 web_search 工具 |
丢掉 |
Codex 的自定义工具 apply_patch |
函数工具(参数 input 是字符串),回复里再还原成 custom_tool_call |
developer 角色 |
user 角色 |
错误按客户端协议返回:Claude Code 收到 {"type":"error","error":{...}}(状态码映射到 invalid_request_error、authentication_error、rate_limit_error、overloaded_error 等),Codex 收到 OpenAI 格式;流开始后的上游错误变成 Claude 的 event: error 或 Responses 的 response.failed。
模型名不在上游目录里、也没有路由规则命中时,直接返回 404 并说明该设哪个变量,不拿每个号去试。目录还没拉到时放行,交给上游判断。
/v1/messages/count_tokens 只做估算:只数 system、messages、tools 里的文本,CJK 字符每字 1 个 token,其余每 4 字节 1 个。Claude Code 用它显示上下文占用,不影响计费。
/v1/models 从上游动态拉取,缓存 1 小时(失败时返回空列表并缓存 5 分钟)。每个模型除了 id,还带上游给出的积分倍率、context_length、max_output_tokens、支持的思考档位与默认档位、能否关闭思考、是否支持工具与视觉等字段。
只在池里企业号自己的目录里出现的模型也会以 cn: 前缀列出,并带 "enterprise_only": true;请求只派给目录里有它的企业号,见企业版模型。
| 写法 | 去向 |
|---|---|
cn:glm-5.2 |
只在国内版账号里选 |
global:gpt-5.5 |
只在国际版账号里选 |
glm-5.2 |
默认走国内版;打开 pool.cross_realm 后按两域模型目录合池,见跨域负载均衡 |
Claude Code、Codex 不配模型时发的是自家官方模型名(如 Claude Code 的 claude-sonnet-4-6),上游目录里多半没有。面板「模型与档位 → 模型路由」可以配一张规则表(存在 config.json 的 model_routes),三个对话端点都按它改写:
- 只改上游目录里没有的名字。目录里有的直通,规则写成
*兜底也不会动到它; - 带
cn:/global:前缀的名字是明确指定,不改;目标可以带前缀; - 从上往下找第一条匹配的规则,只认
*通配,不区分大小写; - 国内版目录拉不到时判断不了,不改,原样交给上游;
- 命中时日志打一行
model route <原名> -> <目标>;目标写错(不在目录里)时,Claude Code / Codex 入口返回 404 并指出是哪条规则。
客户端界面上显示的仍是它自己的模型名,实际用的是规则目标。
curl -s http://127.0.0.1:7863/healthz{"healthy": 2, "total": 3, "service": "workbuddy2api", "realm_servable": {"cn": true, "global": false}}- 200 和 503 的结构相同,只是状态码与计数不同。账号全部在途占满时也返回 503,与对话接口能否受理保持一致。
realm_servable分别给出国内版、国际版此刻能否受理请求,双域部署可以单独告警。- 响应带
X-Service: workbuddy2api头。service字段和这个头用于区分本网关与同端口上的其他服务,避免探活误判。 - 需要强校验时用
/status+api_key:只有持有正确密钥的本网关会返回 200。 - 容器编排的就绪探针请用
/readyz。
每个对话请求结束时,终端输出一行:
| #001 | 18:31:31 | deepseek-v4 | stream | 200 | uid=0851ce35 | TTFB=801ms | tok=60 | 23.5tok/s | total=2.6s | rid=… | out=success | try=1 | credit=0.0123 | src=203.0.113.7 ua="OpenAI/Python" |
| 字段 | 含义 |
|---|---|
#001 |
进程内请求序号 |
18:31:31 |
结束时刻 |
deepseek-v4 |
模型名(过长会截断;跨域选号时写成 域:模型名) |
stream / sync |
流式 / 非流式 |
200 |
状态码 |
uid= |
账号 UID 前 8 位 |
TTFB |
首字节耗时 |
tok / tok/s / total |
输出 token 数 / 速率 / 总耗时 |
rid / out / try / credit |
请求 ID / 结果 / 尝试次数 / 本次扣除积分 |
src / ua |
调用来源 IP 与客户端标签(logging.request_client_info 关闭时不输出) |
这一行只打在终端(docker logs 看得到),面板的「运行日志」不收;同样的信息在面板「请求记录」里,失败的请求还带失败原因(回给客户端的错误消息)。
日志里不出现任何 token、api_key 明文,也不记录提示词和响应内容。请求元数据另外归档到 data/request-logs/,可在面板「请求记录」里查询。
上游均为官方客户端使用的非公开接口,没有公开文档,路径与 Host 以代码里的常量为准:
- 国内版:全部发往官方产品入口
www.workbuddy.cn(对话、token 刷新、登录、模型列表、积分、签到、成长体系、上报)。1.1.2 以前对话一类走copilot.tencent.com、积分一类走www.codebuddy.cn,见官方客户端对照 - 国际版:
www.workbuddy.ai(可用global.chat_base/global.billing_base覆盖)
| 相对路径 | 方法 | 用途 |
|---|---|---|
chat/completions |
POST | 对话补全(SSE) |
/v3/config |
GET | 模型目录(完整路径,不加 /v2);按账号拉,和官方桌面端拉到的是同一份 |
console/enterprises/personal/models |
GET | 企业号另拉的企业模型名单,并进 /v3/config 的目录;个人号不拉 |
plugin/auth/token/refresh |
POST | 刷新 token |
plugin/auth/state · plugin/auth/token · plugin/login/account |
POST / GET | OAuth 登录 |
billing/meter/daily-checkin |
POST | 每日签到 |
billing/meter/get-user-resource |
POST | 余额查询 |
billing/meter/get-enterprise-user-usage |
POST | 企业版额度查询 |
/billing/meter/compensation-status · /billing/meter/claim-compensation |
POST | 国内版个人账号补偿状态与领取(完整路径,不加 /v2) |
report |
POST | 行为与活跃上报 |
activity/growth/buddy/* |
GET / POST | 猫猫旅行:协议、领养、状态、派出、领奖 |
activity/growth/streak · redeem |
GET / POST | 连登天数、档位兑换 |
activity/growth/lottery/summary · draw |
GET / POST | 抽奖次数、抽奖 |
activity/growth/tasks · tasks/accept · tasks/<code>/claim |
GET / POST | 成长任务列表、报名、领奖(领奖走 Web 域) |
聊天请求永远不携带 refresh token,该头只出现在 token 刷新请求里。