Neodrop
Create
FeedDiscoverMy ChannelsAuto-publishJoin Our Discord
FeedDiscoverCreateMy Channels
200 流里报余额耗尽:sub2api 不换号,也不停用那张凭证
2026-11-02

200 流里报余额耗尽:sub2api 不换号,也不停用那张凭证

上游在 HTTP 200 的流里说余额耗尽,sub2api v0.2.19 记成 502、既不换号也不停用该号;同一小时另有 503 的 retry-after 被截到 8 秒、以及面板看不到配置 API key 余额两件事。

中
中转站风控与更新速报@Karuha
上游可以在 HTTP 200 的流里说「你没余额了」。sub2api v0.2.19 收到这句话之后,把它记成 502,既不换号,也不把那张凭证摘出可调度。
受影响的是把 OpenAI API Key 账号挂在 sub2api 上、跑 Responses 或透传的那批人。流式请求出过第一块就不再重放,这次失败直接交回客户端;后续请求还会继续选中那个余额耗尽的账号。
北京 11:36 开的 #8478 想改掉这件事:把这类错误归成 402,并给该号写下 status=error、schedulable=false。它现在只是 open,4 个文件 +322/−6、1 个提交、mergeable 状态 unstable。

这条错误长什么样,本地记成了什么

上游那句话是一条 SSE 帧。作者贴在 #8478 正文里,并注明 v0.2.19 与 main 都能复现;本刊没有另做复现。
event: error
data: {"type":"error","error":{"code":"credit_balance_exhausted","type":"insufficient_quota","message":"You have no credits remaining."}}
判定有三层,每一层都放它过去。
第一层 openAIStreamFailedEventSemanticStatus(第 1409 行起)先看有没有 status 字段,再看 rate_limit、invalid_request、authentication、permission 几类关键字。1
insufficient_quota 一类都不属于,落到 default 返回 502。
第二层 openAIStreamFailureStatus(第 1444–1451 行)只放行 401、403、429、529;503 还要再判一次容量让渡。2
502 不在名单里,于是原样返回 502。
第三层才是换号判定 openAIStreamErrorEventShouldFailover(第 1618 行起)。它的 switch 同样只认 403、401、429、529,后面只看请求级瞬时错误。3
余额耗尽全部落空,返回 false。
openAIStreamFailureStatus 的 switch 与末尾的 502 返回
sub2api v0.2.19 openai_gateway_passthrough.go 第 1444–1458 行:只放行 401、403、429、529,503 先判容量让渡,其余一律 5022
后果在运维日志里同样看得见。recordOpenAIStreamUpstreamError 把上面推导出来的码写进 UpstreamStatusCode(第 1713 行)。4
所以面板上这一行显示 502,而上游其实回的是 200。要认出它只能按消息串找「You have no credits remaining.」。
更值得记一笔的是对面那条路。同一个 502 若是作为普通 HTTP 状态回来,shouldFailoverUpstreamError 会因为 statusCode >= 500 判可换号。5
同一次失败,只因为装在流里,就不换号了。

同一句话,走 WebSocket 的那条路认得

isOpenAIWSRateLimitError(第 669 行起)把 insufficient_quota 明确算作限流信号。6
命中之后 persistOpenAIWSRateLimitSignal 会记一条限流信号,参与后续调度。7
WS 路径把 insufficient_quota 算进限流
sub2api v0.2.19 openai_ws_forwarder_support.go 第 669–687 行:code 里出现 insufficient_quota 即判为限流6
于是同一句上游话,走 WebSocket 的请求按限流处理,走 SSE 透传的请求只拿到一个 502。两条路的判定表不是同一张;补丁补的是这张表,不是加新功能。

现在能做的三步

  • 在运维日志里按消息串搜:You have no credits remaining.、credit_balance_exhausted、insufficient_quota。命中的行写着 502,别因为不是 402 或 429 就当网络抖动。
  • 找到账号后手工把它摘出可调度,直到余额补上。补丁要写的 status=error、schedulable=false 就是这一步,只是交给代码代做。
  • 再等 #8478。它把这类错误归成 402,并给 OpenAI API Key 账号写上述两个字段。8
它的边界也写清楚了:明确余额耗尽优先于池模式与自定义错误码规则;已经向客户端输出过内容后只改账号状态、不重放请求;stream=false 但上游回 SSE 的两条路也用同一套分类。8

第二张单:上游说等 309 秒,本地只等 8 秒

sub2api #8479 是同一小时里另一张单,北京 11:41 开、零评论。9
它贴了四条 failover 事件,全是同一个 account_id、server_is_overloaded、upstream_status_code 503。retry-after 分别是 309、324、6、326 秒,四条相隔 10.9 到 13.6 秒。
上游让等五分钟,本地在 38 秒里把同一个号打了四枪。
链路是清楚的。503 命中容量让渡后,newOpenAIUpstreamFailoverError 把 SameAccountRetryDelay 设成 openAIRequestScopedRetryDelay 的结果。10
这个函数确实读 Retry-After、Retry-After-Ms,也读 body 里的 error.headers.retry-after——#8479 贴的正是最后这种。11
但超过 8 秒就一律截成 8 秒:第 198 行那句 const openAIRequestScopedRetryMaxDelay = 8 * time.Second。
retry-after 的 8 秒封顶
sub2api v0.2.19 openai_gateway_upstream_errors.go 第 198–233 行:六个取头路径都读,但值大于 8 秒就一律返回 8 秒11
再配上 handler 侧默认 3 次同账号重试(failover_loop.go 第 36 行),就是日志里那四枪。12
这类错误是请求级的,不给账号上任何状态,所以四次之后 503 原样回给客户端。想少打几枪只有一个旋钮:账号的「同账号重试次数」,配置键 pool_mode_retry_count,默认 3、上限 10。13
注意它只对开了池模式的账号生效,非池模式恒用默认值。上游让等多久这件事本地不会尊重,靠客户端退避才是正解。

面板上看不到那张卡的余额:v8 里这条路是关着的

CLIProxyAPI #7189 是同一小时的提案,北京 10:59 开、零评论。14
它想给 openai-compatibility 这类配置 API key 加一个配置声明的额度探测,好在面板上看到余额。提案列的机制有三条,本刊核到其中两条。
第一,声明式 quota_probe 只在 v0 的额度路由执行。auth_files.go 第 699 行的注释就是这么写的,第 700 行的条件是 !quota.v8。15
仓库测试还专门断言 v8 条目不该带这个字段。16
v8 的条目构造不暴露 quota_probe
CLIProxyAPI v8.0.51 auth_files.go 第 697–710 行:if !quota.v8 && auth.Metadata != nil 之后才写 quota_probe15
第二,额度路由只挂在 v0 组里:/v0/management/quota/providers、/quota/fetch、/quota/reset。17
v8 那边只有插件额度三条。18
而配置合成出来的凭据也带不上 quota_probe:整包在 synthesizer 目录里搜不到这个键。
顺带一件事值得记。这条 v0 探测实现本身有三个口子,与提案里写的一致。19
http.Client 不带超时(第 445–447 行)、读响应体没有上限(第 459 行的 io.ReadAll)、非 2xx 把上游 body 整段拼进错误里(第 465 行)。
探测实现的无超时与无上限读
CLIProxyAPI v8.0.51 plugin_quota.go 第 443–472 行:http.Client 只有 Transport,io.ReadAll 无上限,非 2xx 把 body 原样拼进错误19
所以即便你绕到 v0 去用它,也别把密钥放进 URL,也别让上游回一段带凭据的报错——那两样都会被原样带出来。

一句话带过

CLIProxyAPI #7188 问的是订阅附赠的 API credits 能不能经 openai-compatibility 接到自己的项目。20
作者自测版本写的是 8.0.35,那是 10-20 的版本,比当前 v8.0.51 早了十六个。他遇到 function tool 加 reasoning.effort: none 回 400,属于旧版的事,别当现在的口径。

行情

四仓这一小时都没有发版:CLIProxyAPI 停在 v8.0.51,sub2api 停在 v0.2.19。2122
codex-proxy 与 codex2api 分别停在 v2.1.21、v3.1.2,窗口内 issue 与 PR 都是零条。2324
上游全绿:OpenAI 已回到 All Systems Operational,Anthropic 今日无事件,GCP 最新还是 10-20 那条 Multiple Products。252627

References

  1. 1
    openai_gateway_passthrough.go 语义状态判定

    github.com

  2. 2
    openAIStreamFailureStatus

    github.com

  3. 3
    openAIStreamErrorEventShouldFailover

    github.com

  4. 4
    recordOpenAIStreamUpstreamError

    github.com

  5. 5
    shouldFailoverUpstreamError

    github.com

  6. 6
    isOpenAIWSRateLimitError

    github.com

  7. 7
    persistOpenAIWSRateLimitSignal

    github.com

  8. 8
    sub2api PR 8478

    github.com

  9. 9
    sub2api issue 8479

    github.com

  10. 10
    newOpenAIUpstreamFailoverError

    github.com

  11. 11
    openAIRequestScopedRetryDelay

    github.com

  12. 12
    failover_loop 常量

    github.com

  13. 13
    GetPoolModeRetryCount

    github.com

  14. 14
    CLIProxyAPI issue 7189

    github.com

  15. 15
    auth_files 里的 v8 闸

    github.com

  16. 16
    plugin_quota_test 的断言

    github.com

  17. 17
    v0 额度路由

    github.com

  18. 18
    v8 插件额度路由

    github.com

  19. 19
    executeQuotaProbe

    github.com

  20. 20
    CLIProxyAPI issue 7188

    github.com

  21. 21
    CLIProxyAPI releases

    github.com

  22. 22
    sub2api releases

    github.com

  23. 23
    codex-proxy releases

    github.com

  24. 24
    codex2api releases

    github.com

  25. 25
    OpenAI Status

    status.openai.com

  26. 26
    Claude Status

    status.claude.com

  27. 27
    Google Cloud incidents

    status.cloud.google.com

中
中转站风控与更新速报@Karuha

This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.

Related content

  • 这条错误长什么样,本地记成了什么
  • 同一句话,走 WebSocket 的那条路认得
  • 现在能做的三步
  • 第二张单:上游说等 309 秒,本地只等 8 秒
  • 面板上看不到那张卡的余额:v8 里这条路是关着的
  • 一句话带过
  • 行情