信任与审计
每一笔扣费 — 怎么算、怎么查、怎么保证不重复
我们的计费是 post-settlement 模型:先把请求转发到上游,**拿到真实的 usage 数据后再扣积分**。这意味着扣费金额永远等于上游真实消耗对应的 tier,不是估算。下面把每个步骤摊开。
1. 扣费公式
每个模型在 cc_models 表里有一个 context_tiers 配置 —— up_tokens 阈值映射到 credits。BillingHandler 拿到上游返回的 prompt_tokens + completion_tokens 后,用 input_tokens 落档,扣对应 credits:
// internal/service/model_registry.go
func (m ModelConfig) CreditsForTokens(inputTokens int) int {
for _, tier := range m.ContextTiers {
if tier.UpTokens == 0 || inputTokens <= tier.UpTokens {
return tier.Credits
}
}
return m.ContextTiers[len(m.ContextTiers)-1].Credits
}
// Example — claude-sonnet-4-6 with 18,000 input tokens:
// tier 1: up_tokens=32000 credits=12 ← matches
// tier 2: up_tokens=200000 credits=36
// tier 3: up_tokens=0 credits=84 (terminal "anything bigger")
// → 12 credits deducted, regardless of completion length.完成 tokens 不影响落档 — 这样长回答不会变成意外账单。
2. 一次请求的完整生命周期
- 01你的客户端发请求到 api.clawfeeder.ai,header 里带 Authorization: Bearer cf-sk-...
- 02JWT/API Key middleware 校验后,BillingHandler 读 request body 拿到 model 字段
- 03查 cc_models 注册表确认 model 存在(否则拒 400 unsupported_model)
- 04余额 + trial 门禁检查 — 不够 / 不被授权直接 402
- 05向真实上游(channel 链路)转发,5xx 自动 fallback 到下一个 channel
- 06**上游返回后**,streaming usage scanner 边转发边扫 usage 字段
- 07拿到 usage 后调 CreditsForTokens 算出扣多少
- 08写一行 cc_credits_ledger(reason=api_use, delta=负数, model, latency_ms, status_code, ref_id=trace_id)
- 09扣款 unique ref_id 保证幂等 — 同一 trace_id 重复 settle 不会重复扣
3. 在哪可以核查每一笔
每一次请求都能从 4 个独立维度交叉验证:
- ✓ 响应头里的 X-Request-ID每次请求服务器返一个 UUID 给你,这是 ledger 那一行的 ref_id,也是日志 trace_id。任何时候出问题,把它发给我们就能定位。
- ✓ 响应头里的 X-Clawfeeder-Model实际结算的真实模型 ID。用 model=auto 时,会显示路由到了哪个具体模型。计费就按这个,与 'auto' 无关。
- ✓ Dashboard 用量页/dashboard/usage 显示每笔扣费(模型、tokens、credits、ref_id、状态码)。可以直接查 30 天历史。
- ✓ GET /api/credits/history程序化访问同一份数据,JSON 输出,方便接你的对账脚本。
4. 幂等保证
cc_credits_ledger.ref_id 列有 UNIQUE partial index。一次请求只有一个 trace_id,即使 BillingHandler 因为某种原因被重试(比如部署时 graceful shutdown 中途,或者 streaming SSE 重连),第二次写入 ledger 会被数据库拒绝,扣款不会重复发生。
-- migration 013 (deployed 2026-04-20): CREATE UNIQUE INDEX cc_credits_ledger_ref_id_idx ON cc_credits_ledger(ref_id) WHERE ref_id IS NOT NULL;
视频任务异步扣费也是同一套机制 — 预扣 ref_id=video:<task_id>,结算 ref_id=video-settle:<task_id>,退款 ref_id=video-refund:<task_id>。同一 task_id 不会被结算两次。
5. 我们存了什么、没存什么
✓ 我们存
- · 请求时间、模型、tokens 数量
- · 扣的积分、ref_id、上游 HTTP 状态码
- · 上游延迟(latency_ms)
- · 用户 ID 和 API key 指纹
✗ 我们不存
- · Prompt 内容
- · Response 内容
- · 请求 / 响应 body 任何片段
- · (Playground 对话除外 — 那是你主动保存的)
这是技术不变量,不是承诺。BillingHandler 的代码里没有任何把 body 写到 DB 的路径 — 它只走 streaming scanner 找 usage 字段,scan 完丢弃。
6. 余额为零会发生什么
Post-settlement 计费意味着扣款发生在请求之后。我们允许一个 -100 积分的软透支底线 — 这样长请求拿到 usage 时不会因为余额刚好为 0 而被吞掉。一旦余额 ≤ -100,新请求立刻返 402 直到充值。
底线 -100 积分 = 约 $0.30 — 实际超的钱永远很小。
7. 积分有效期与续费
随订阅发放的积分(套餐充值、兑换码)与订阅周期同寿命 — 到期时该批未用完的部分会被清零。这是订阅制的常态;关键在于续费的处理方式对你有利。
- · 提前续费,有效期叠加 — 续费时间从「当前到期日」往后加,不是从今天重算。早续不丢剩余天数。
- · 未用完的积分自动顺延 — 续费时,你账上还没用完的积分会跟着展期到新的到期日,一分不丢。
- · 赠送类积分不过期 — 邀请、推荐、运营赠送的积分没有过期时间,永久有效。
例:会员 7-15 到期、还剩 3,200 积分,你在 7-1 续费 30 天 → 有效期叠加到 8-14,那 3,200 积分也顺延到 8-14。
8. 数据一致性自检
每天 03:17 UTC 一个 reconcile job 跑 4 项只读检查:
- ·
duplicate_ref_ids— 扫描 ledger 是否有重复 ref_id - ·
no_usage_trend— 检查零费用 ledger 行的趋势(可能 usage parse 失败) - ·
tier_distribution— tier 分布是否异常偏向某档 - ·
balance_drift— 用户余额 = SUM(delta) 是否对得上
结果写 Redis 30 天保留。任何异常 finding 会写 WARN/ERROR 日志,可由后续运维流程接告警。
想自己看一笔扣费?
登录后到 Dashboard 用量页查最近 30 天的每一行 ledger。