用量和预算
面向组织管理员。每项任务需要对应权限。只有平台管理员可以修改 /v1/platform
下的设置。从管理员手册开始。
OpenBot 会记录每一次模型调用。你可以看到每个成员、每个 Bot 和每个用户令牌用了多少 token,以及花了多少钱。token 是模型读或写的一小段文本。你也可以设置预算。花的钱太多时,预算会停止模型调用。平台可以给组织额度,用来支付平台共享的模型。
| 词 | 含义 |
|---|---|
| 用量事件 | 一次模型调用:它的 token、它的费用,以及谁发起了它。 |
| 价格 | 一个模型在一个提供方那里,一百万个 token 的费用。 |
| 预算 | 组织、成员、Bot 或用户令牌的花费上限,按一天或一个月计算。 |
| 额度 | 平台给组织的钱,用来支付平台的模型。 |
| 微美元 | API 中所有的钱都以微美元(micro-USD)为单位。1 美元等于 1,000,000 微美元。 |
| 平台密钥 | 一个共享的模型提供方。由平台付费。 |
| 自己的密钥 | 组织自己的模型提供方。由组织直接向模型公司付费。 |
ChatGPT 提供方沿用普通模型提供方的价格、预算和点数规则。组织提供方不扣平台点数。平台提供方按设置的价格和点数规则处理。ChatGPT 账户额度也同时适用。
OpenBot 记录什么
Section titled “OpenBot 记录什么”OpenBot 会在向提供方发送请求前记录模型调用。每个用量事件都有一个 source:
source |
是什么发起了调用 |
|---|---|
turn |
Chat 中一个 Turn 的一个模型步骤。 |
compaction |
对旧消息的总结,让对话能放进模型。 |
judge |
评判 Rules 的模型。 |
bot_memory |
记忆 Bot:它学习、补充和回答时的用量。 |
other |
其他任何调用。 |
当调用有这些信息时,用量事件还会带有 Chat(chatId)、Turn、Bot、成员和用户令牌。在
Channel 中,成员是写这条消息的人。
每次调用都有稳定的 id。持久步骤还带有 executionId。运行时,status 是
pending;提供方确认用量后是 confirmed;缺少用量时是
unknown。未知 token 数和费用返回
null,不写成零。同一份已确认用量不会重复扣费。
发送前,OpenBot 将估算费用放入
reservedMicros。输入文本按约四个字符一个 token 估算,输出暂按 1,024 个 token 估算。其他输入部分使用粗略大小。实际费用可能更高。调用结束后,原始估算保留在
estimatedMicros 中。如果旧记录已没有估算金额,该字段为 null。
pending 和 unknown
调用都会保留预留。执行结束、停止或更换执行者,都不会释放未知调用的预留。只有已确认的回执才能结算实际费用并释放预留。回执可以明确确认零费用,缺少用量不等于零费用回执。迟到的回执只结算一次。汇总分别计算已确认的
calls、pendingCalls 和 unknownCalls。不调用模型的宿主操作没有模型用量。
模型计费和 token 速率错误会把完整原因保存到加密错误记录。公开错误和普通日志只包含安全分类和错误 ID。如果已确认用量写入失败,加密记录还会保留调用 ID 和 token 数量,以供修复。未知用量不会因此被记为零费用。
无效的分页游标会返回
400 UsageQueryInvalid。价格至少需要一个阶梯,因此每个 token 数量都有对应价格。
一个价格属于一个提供方的一个模型,因为同一个模型在另一个提供方那里的费用可能不同。很多模型在请求很长时更贵。有些提供方在夜间更便宜。所以一个价格有按输入大小划分的档位(tier),也有按一天中的时间划分的时段(period)。
每个档位都有一组费率。每个费率是一百万个 token 的费用,单位是微美元:
| 字段 | 哪些 token |
|---|---|
inputUncached |
提供方没有从缓存读取的输入 token。 |
inputCacheRead |
提供方从缓存读取的输入 token。 |
inputCacheWrite |
提供方写入缓存的输入 token。 |
output |
输出的文本和工具调用。 |
outputReasoning |
推理 token。发送 null 时,使用 output 的费率。 |
组织的管理员为组织自己的提供方设置价格。这些价格是对模型公司收费的估计。平台管理员为共享的提供方设置价格。在下面的例子中,一百万个 token 的输入费用是 2.50 美元,输出费用是 10 美元。这个价格适用于所有输入大小和所有时间:
curl -X PUT -H "Authorization: Bearer $OPENBOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-4.1","tiers":[{"upToInputTokens":null,"inputUncached":2500000,"inputCacheRead":625000,"inputCacheWrite":0,"output":10000000,"outputReasoning":null}]}' \ https://api.example.com/v1/organization/model_providers/prov_4c1e/prices| 路由 | 作用 |
|---|---|
GET /v1/organization/model_providers/:providerId/prices |
列出你自己的提供方中每个模型的当前价格。 |
PUT /v1/organization/model_providers/:providerId/prices |
为你自己的提供方中的一个模型设置新价格。 |
GET /v1/platform/model_providers/:providerId/prices |
列出一个共享的提供方中每个模型的当前价格。 |
PUT /v1/platform/model_providers/:providerId/prices |
为一个共享的提供方中的一个模型设置新价格。 |
提供方只能为它关联的模型设置价格。其他模型会返回 400 和
ModelProviderInvalid。模型目录会显示每个模型当前的
price。
新价格只对从现在起开始的调用生效。每次预留都从数据库读取价格,并保存一份副本。其他节点调价或移除提供方,都不会改变这次调用的价格。用量记录保留已结算费用。OpenBot 会把每次调用的费用向上取整到整数微美元。
没有价格的模型费用为 0。它的调用仍然会被记录,但不计入预算,也不扣除额度。
按输入大小划分的档位
Section titled “按输入大小划分的档位”tiers 是一个列表,从小到大排列。每个档位都有
upToInputTokens:这个档位中一次调用最多可以有多少个输入 token。一次调用的输入 token 是它全部的输入 token,不管是否来自缓存。最后一个档位的
upToInputTokens 为 null,因为它没有上限。你最多可以有 20 个档位。
一次调用使用它的输入能放进的第一个档位。然后这次调用的所有 token 都按这个档位的费率计费,而不只是超出上限的那部分 token。大多数提供方都这样对长请求收费。
在下面的例子中,一个模型在 32,000 个输入 token 以内较便宜,在 128,000 个以内更贵,超过 128,000 个时最贵:
{ "model": "qwen-plus", "tiers": [ { "upToInputTokens": 32000, "inputUncached": 800000, "inputCacheRead": 160000, "inputCacheWrite": 0, "output": 2000000, "outputReasoning": 8000000 }, { "upToInputTokens": 128000, "inputUncached": 2400000, "inputCacheRead": 480000, "inputCacheWrite": 0, "output": 6000000, "outputReasoning": 24000000 }, { "upToInputTokens": null, "inputUncached": 4800000, "inputCacheRead": 960000, "inputCacheWrite": 0, "output": 12000000, "outputReasoning": 48000000 } ]}一次有 50,000 个输入 token 的调用使用第二个档位。
按一天中的时间划分的时段
Section titled “按一天中的时间划分的时段”periods 为某些时间给出另外的档位,比如夜间的较低价格。每个时段都有一个
name、一个或多个 windows(时间窗口),以及它自己的 tiers。timeZone
说明时间窗口使用哪个时区。它是一个 IANA 名称,比如 UTC 或
Asia/Shanghai。不写它时,使用 UTC。
| 窗口字段 | 是什么 |
|---|---|
start |
开始时间,格式为 HH:MM。窗口包含这一分钟。 |
end |
结束时间,格式为 HH:MM,最大为 24:00。窗口在这一分钟之前结束。 |
days |
窗口在哪些天开始:mon、tue、wed、thu、fri、sat、sun。不写它时,表示每一天。 |
当 end 早于 start
时,窗口会跨过午夜。它属于它开始的那一天。例如,{"start":"22:00","end":"02:00","days":["fri"]}
包含周五 23:00 和周六 01:00,但不包含周五 01:00。
OpenBot 使用调用开始的时间。它使用第一个有窗口包含这个时间的时段。没有时段包含这个时间时,调用使用价格本身的
tiers。在两种情况下,调用都再按输入大小选择档位。
在下面的例子中,一个提供方在北京时间 00:30 到 08:30 收半价:
{ "model": "deepseek-chat", "timeZone": "Asia/Shanghai", "tiers": [ { "upToInputTokens": null, "inputUncached": 2000000, "inputCacheRead": 500000, "inputCacheWrite": 0, "output": 8000000, "outputReasoning": null } ], "periods": [ { "name": "off-peak", "windows": [{ "start": "00:30", "end": "08:30" }], "tiers": [ { "upToInputTokens": null, "inputUncached": 1000000, "inputCacheRead": 250000, "inputCacheWrite": 0, "output": 4000000, "outputReasoning": null } ] } ]}价格在所有时间都相同时,不写 periods。
GET /v1/organization/usage 把一段时间内的 token 和费用加起来。它需要 Usage
上的 read。
| 查询参数 | 作用 |
|---|---|
from |
开始时间,使用 ISO 8601 格式。默认是 UTC 时间当前月份的开始。 |
to |
结束时间,使用 ISO 8601 格式。默认是现在。 |
groupBy |
day(默认)、model、provider、member、bot、token 或 source。 |
没有时区的时间(例如 2026-10-01T00:00:00)按 UTC 计算。
curl -H "Authorization: Bearer $OPENBOT_TOKEN" \ "https://api.example.com/v1/organization/usage?groupBy=member"{ "from": "2026-10-01T00:00:00.000Z", "to": "2026-10-23T09:00:00.001Z", "groupBy": "member", "total": { "calls": 42, "inputUncached": 81200, "inputCacheRead": 20400, "inputCacheWrite": 0, "outputText": 9800, "outputReasoning": 1200, "costMicros": 325250 }, "items": [ { "key": "u_123", "calls": 42, "inputUncached": 81200, "inputCacheRead": 20400, "inputCacheWrite": 0, "outputText": 9800, "outputReasoning": 1200, "costMicros": 325250 } ]}key
是分组的值,比如一个用户 ID 或一天。对于没有这个值的调用,比如没有 Bot 的调用,它为
null。
GET /v1/organization/usage/events 列出单个用量事件,最新的在前。它接受
from、to、limit(1 到 200,默认 50)和 after。把一页的 next 值作为
after 发送,就能得到下一页。每个事件都有 priced 和
charged。当费用从额度中扣除时,charged 为 true。
预算会按花费上限检查模型调用。你可以在四种范围上设置预算:
scope |
scopeId |
计入什么 |
|---|---|---|
organization |
不写它。它就是组织 ID。 | 组织的每一次调用。 |
member |
一个成员的用户 ID。 | 为这个成员工作的调用。 |
bot |
一个 Bot 的 ID。 | 这个 Bot 的调用。 |
token |
一个用户令牌的 ID。 | 这个令牌启动的工作中的调用。 |
每个预算都有一个 period(周期):day 或
month。两者都从 UTC 午夜开始。每个范围在每种周期上只能有一个预算。
每个预算还有一个 mode:
hard预算:把本周期内的已确认费用和所有占用金额计入后,如果剩余额度放不下估算费用,就停止模型调用。调用会失败,所以 Turn 会因为模型错误而失败。soft预算:从不停止调用。它只显示exceeded: true。
预算计入每一次有价格的调用,也包括使用组织自己密钥的调用。调用前,OpenBot 一起检查已确认花费和未释放的预留。所有节点共享这项检查。估算费用放不进剩余预算时,调用停止。新的一天或一个月开始后,
pending 和 unknown
调用的预留仍计入预算。只有确认费用后才能结算并释放预留,明确的零费用回执也算确认。同一份回执不会重复扣费。已确认费用计入回执记录时所在的周期。最终费用可能高于估算,因此预算不能保证提供方的最终扣费不超过上限。
旧的 unknown 调用只要保存了 estimatedMicros,即使 reservedMicros
为0,估算仍计入占用金额。缺失的旧估算保持为
null。如果旧的未知调用没有估算且可能产生费用,OpenBot 会在费用确认前,阻止其所匹配的硬预算下的新调用。平台调用还会冻结受影响的平台额度。已保存的零估算不会触发这种缺失估算冻结,也不会新增预留。已知免费的旧调用也不会触发这种冻结。其他预算和额度限制仍适用于新调用。
curl -H "Authorization: Bearer $OPENBOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"scope":"bot","scopeId":"bot_7d2c","period":"month","limitMicros":50000000,"mode":"hard"}' \ https://api.example.com/v1/organization/budgets{ "id": "budget_5c9e", "scope": "bot", "scopeId": "bot_7d2c", "period": "month", "limitMicros": 50000000, "mode": "hard", "periodStart": "2026-10-01T00:00:00.000Z", "spentMicros": 1250000, "exceeded": false, "version": 1, "createdAt": "2026-10-23T09:00:00.000Z", "updatedAt": "2026-10-23T09:00:00.000Z"}| 路由 | 作用 |
|---|---|
GET /v1/organization/budgets |
列出预算,以及它们现在的花费。 |
POST /v1/organization/budgets |
添加一个预算。 |
GET /v1/organization/budgets/:budgetId |
读取一个预算。 |
PATCH /v1/organization/budgets/:budgetId |
修改 limitMicros 或 mode。把 version 作为 expectedVersion 发送。 |
DELETE /v1/organization/budgets/:budgetId |
移除一个预算。 |
读取预算需要 Budget 上的 read。修改预算需要 Budget 上的
write。每次修改都会写入审计日志。
平台可以用额度出售或赠送共享模型的使用。额度默认是关闭的。这时,组织可以无限制地使用共享模型,除非预算停止了它。
平台管理员为一个组织打开额度,并给出额度:
curl -X PATCH -H "Authorization: Bearer $OPENBOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"enabled":true}' \ https://api.example.com/v1/platform/organizations/org_8f3a/credits
curl -H "Authorization: Bearer $OPENBOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"amountMicros":20000000,"note":"Trial for October"}' \ https://api.example.com/v1/platform/organizations/org_8f3a/credits/grants负的 amountMicros 会收回额度。每次给出额度都会写入平台审计日志。
额度打开时:
- 每次已确认的平台模型调用,都会从余额中扣除它的费用。
- 扣除所有占用后,如果没有可用余额,或余额放不下估算费用,就停止调用。使用组织自己密钥的调用不受平台额度限制。
- OpenBot 在调用前检查余额和占用金额。已确认的回执从余额扣除实际费用,并释放占用。费用高于估算时,余额可能低于 0。
pending和unknown调用都会保留预留。未知用量不会减少已确认余额,但被占用的额度不能再次使用。迟到的已确认回执只扣除一次实际费用并释放预留,这可能使余额低于0。- 旧的平台未知调用如果没有估算且可能产生费用,会在费用确认前阻止新的平台调用。已保存的零估算和已知免费的旧调用不会触发这种缺失估算冻结,也不会新增预留。其他额度限制仍适用于新调用。
使用组织自己密钥的调用永远不会扣除额度,即使额度是打开的。
GET /v1/organization/credits 显示 enabled、balanceMicros
和最新的 50 次给出记录。它需要 Usage 上的 read。平台管理员用
GET /v1/platform/organizations/:organizationId/credits 读取同样的内容。
OpenBot 还没有支付功能。出售额度的平台在自己的系统中记录付款,然后用 API 给出额度。
调用被停止时
Section titled “调用被停止时”当预算或额度停止一次调用时,模型调用会失败,返回原因为 QuotaExhaustedError 的
AiError。Turn 以状态 Failed 和代码 model_error 结束。错误的元数据在
openbot.reason 中说明原因:budget 或 credits。对于预算,openbot.budgetId
指明是哪个预算。
当 OpenBot 无法读取预算时,比如数据库宕机,它也会停止调用。
平台管理员可以限制每个调用方在一分钟内发送多少个 API 请求。这就是速率限制。每个用户令牌单独计数。没有令牌的已登录用户算作一个调用方。所有 OpenBot 实例共享这个计数。在平台设置中设置
rateLimit:
curl -X PATCH -H "Authorization: Bearer $OPENBOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"expectedVersion":4,"rateLimit":{"requestsPerMinute":600}}' \ https://api.example.com/v1/platform/settingsnull 表示没有限制。这是默认值。超过限制的调用方会得到 429 和
RateLimited。retryAfterSeconds
说明下一分钟什么时候开始。这个限制涵盖组织的 API。它不涵盖
/v1/platform、登录和 webhook。
token 速率
Section titled “token 速率”token 速率限制一个范围内的模型工作使用 token 的速度。它把输入 token 和输出 token 一起计数。工作超过这个速率时,OpenBot 会让它变慢。OpenBot 从不拒绝这些工作。
每个范围都有一个令牌桶(token bucket)。桶最多装 burstTokens
个 token,并按 tokensPerSecond
的速度重新装满。满的桶让一小段突发的工作以全速通过。
| 范围 | 一个桶对应 |
|---|---|
organization |
组织的所有模型调用。 |
member |
为一个成员工作的模型调用。 |
bot |
一个 Bot 的模型调用。 |
token |
一个用户令牌启动的工作中的模型调用。 |
一次模型调用从它所属的每个范围的桶中取 token:
- 调用之前,它从每个共享桶中取出输入估算量,并等待轮到自己。非流式调用还会取出一个粗略的输出估算量。等待结束后,OpenBot 才检查预算和额度。
- 模型流式输出时,OpenBot 把提供方的输出读进一个缓冲区。只有当桶里有足够的空间时,它才把每一段发出去,大约每四个字符算一个 token。所以输出以均匀的速度到来。
- 调用结束时,已确认的数量会替换估算量。额外的 token 会增加欠账,未使用的估算量会放回桶中。即使在等待输出时停止调用,也会使用提供方已返回的数量。没有确认数量时,速率限制保留估算量。这不会扣款。
平台管理员在平台设置中设置最高的速率。组织可以在自己的设置中设置自己的速率。对每个范围,两个速率中较低的那个生效,所以组织只能把速率调低。null
表示这个范围没有限制。默认没有限制。
curl -X PATCH -H "Authorization: Bearer $OPENBOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"expectedVersion":5,"tokenRate":{"organization":{"tokensPerSecond":2000,"burstTokens":20000},"member":{"tokensPerSecond":500,"burstTokens":10000},"bot":null,"token":null}}' \ https://api.example.com/v1/platform/settings所有实例共享 PostgreSQL 中的桶。所有范围的 token 在同一个事务中取出,补充 token 使用数据库时钟。增加实例不会提高一个范围的速率。停止等待中的调用不会清除它的共享欠账。数据库故障时,模型调用暂停进入,直到可以再次检查速率。
| 状态 | 错误 | 何时 |
|---|---|---|
400 |
UsageQueryInvalid |
from 或 to 不是 ISO 8601 时间,或者 from 不在 to 之前。 |
400 |
BudgetInvalid |
缺少范围 ID,或者组织中没有成员或令牌有这个 ID。 |
400 |
ModelProviderInvalid |
提供方没有关联你要设置价格的模型。 |
403 |
Forbidden |
调用方不能对 Usage 或 Budget 做这件事。 |
404 |
BudgetNotFound |
组织中没有预算有这个 ID。 |
409 |
BudgetConflict |
这个范围在这个周期上已经有预算,或者预算已经被修改。 |
429 |
RateLimited |
调用方在这一分钟内发送了太多请求。 |
503 |
BillingUnavailable |
OpenBot 无法连接它的数据库。 |
在 Usage 参考和 Budgets 参考中查看完整的结构。