Grok 成本追踪怎么做:从单次请求拆到团队账单
数据截至 2026-08,价格与限额以各官网为准。本文只讲计费与接入机制,不列具体价格数字。
Grok 的成本追踪比多数平台好做一件事:官方文档明确写了,xAI API 的每一个推理响应都会在 usage 对象里带回这次请求实际被计费的金额,字段名是 cost_in_usd_ticks,chat completions、Responses API、图像生成、视频生成的响应都有。它是「这一次请求」的最终计费额,已经把提示缓存带来的减免、以及服务端工具调用的费用全部算进去了,不需要你事后再去账单里对账。 所以一套可落地的拆法是三层:请求层用 cost_in_usd_ticks 打点写进你自己的日志,团队层用控制台的 Usage Explorer 按 API Key、模型、维度分组看,自动化层用 Management API 的用量分析接口把数据拉进自己的看板。下面按这个顺序讲,每层都说清字段名和它的边界。
第一层:请求响应里那个自带成本的字段
官方 Cost Tracking 文档给出的机制是:成本按请求返回,不管这次调用是一个简单补全、一次流式响应,还是一个带服务端工具的 agentic 循环,返回的都是这一次调用的实际计费额。文档特别强调这是「实际被计费的金额」,而不是估算值,也不需要事后再做账单查询。
数值用一种叫 ticks 的整数单位表达。为什么不直接给小数?官方给的理由很实在:ticks 的存在是为了精度,它能表示到远小于一分钱的量级而不引入浮点舍入误差——当你要处理成千上万次请求、并且要求汇总数字能对得上账时,这个差别就不是学术问题了。ticks 与法定货币之间是固定的整数换算关系,换算系数写在官方 Cost Tracking 页上,以官方文档当前版本为准。
读取方式有两种。用 xAI 官方 SDK 时,响应对象上有一个 cost_usd 便捷属性,会自动把 ticks 换算好;如果你要做整数精度的记账,原始值仍然可以从 response.usage.cost_in_usd_ticks 取。用 OpenAI 兼容 SDK 或直接发 REST 请求时,usage 对象里直接就有 cost_in_usd_ticks。
这里有一个容易踩的坑,官方文档专门用 NOTE 标出来了:Vercel AI SDK(@ai-sdk/xai)目前不会在它的响应元数据里透出 cost_in_usd_ticks。如果你的服务是用它接的,想拿成本就得改用 OpenAI SDK 或者直接调 REST。这类「某个包装层把字段吃掉了」的问题,排查起来最费时间,提前知道能省一天。
流式响应的成本在哪一块
流式是成本打点最容易漏的地方,两条链路的行为还不一样。
用 xAI SDK 流式时,每个 chunk 上带的 cost_in_usd_ticks 是一个不断累进的总额,最后一个 chunk 反映的是这次请求的最终成本;SDK 组装出来的 Response 对象会自动带上这个值,所以你在流结束后直接读响应对象即可。
用 OpenAI SDK 或原始 REST 流式时,必须在请求里显式设置 stream_options: { include_usage: true },否则你一个成本字段都拿不到。而且官方写得很明白:成本只出现在最后一个 chunk 里,那个 chunk 的 choices 是空的;中间的 chunk 不含任何 usage 数据。所以消费流的循环里要写成「先判断 chunk.usage 存不存在,存在就记账,否则才去取 choices[0].delta.content」,顺序反了就会漏掉最后一块。
多轮会话:这个字段不会自己累加
cost_in_usd_ticks 是严格的 per-request 值,官方文档明确写了它不会跨轮累积。多轮对话的总成本要你自己在应用侧求和——每轮拿到响应后累加,会话结束时输出总额。
这条看着像废话,但它决定了你日志表的结构:成本应该记在「请求」这一行上,会话总额是聚合出来的,而不是反过来。如果你一开始就把成本挂在会话维度上,后面想按模型、按接口拆就拆不出来了。想把这套记账和月度预算对上,可以配合 API 成本监控怎么做 那篇讲的通用打点思路一起看。
Agentic 请求:计费的是哪些工具调用
带服务端工具(Web 搜索、X 搜索、代码执行等)的请求,成本归集规则最需要交代清楚。官方给的结论是:这类请求返回的 cost_in_usd_ticks 是一个单值,覆盖了这次请求里所有的模型解码成本和所有工具调用成本,你不需要再单独累加。
但要拆出「钱花在哪个工具上」,就得看两个不同的字段,官方 Tool Usage Details 页把它们的区别讲得很细:
tool_calls:这次 agentic 过程中所有被尝试的工具调用,每一项含id、function.name、function.arguments,包括失败的那些。server_side_tool_usage:成功执行的工具及其调用次数的映射,官方原文写的是它「决定你的计费」。
两者会不一致的场景,官方列了三类:工具执行失败(比如去浏览一个不存在的网页、抓一条已删除的 X 帖子)、参数非法无法处理、以及工具执行链路的临时网络或服务故障。官方明确写了失败的尝试不计费。所以做成本归因时,要按 server_side_tool_usage 拆,按 tool_calls 拆会高估。
server_side_tool_usage 里的键是一组高层分类枚举,官方文档列出的取值包括 SERVER_SIDE_TOOL_WEB_SEARCH、SERVER_SIDE_TOOL_IMAGE_SEARCH、SERVER_SIDE_TOOL_X_SEARCH、SERVER_SIDE_TOOL_CODE_EXECUTION、SERVER_SIDE_TOOL_VIEW_X_VIDEO、SERVER_SIDE_TOOL_VIEW_IMAGE、SERVER_SIDE_TOOL_COLLECTIONS_SEARCH 和 SERVER_SIDE_TOOL_MCP(以官方文档为准)。而 tool_calls 里的函数名是被调工具的精确名字,两者是分类与实例的关系——比如 Web 搜索这一类下面对应多个具体函数名。做看板时按分类聚合更稳,按函数名聚合容易被上游改名打断。
agentic 请求的 token 口径也不一样
同一页还讲了一件反直觉的事:agentic 请求的 completion_tokens 只代表模型最终的文本输出,通常比你预期的小得多,因为中间的推理和工具编排都在内部完成了。而 prompt_tokens 是整个 agentic 过程中所有推理请求的累计输入,每一步都带上截至那一步的完整对话历史,所以它会随着 agent 推进而膨胀。
另外两个字段值得单独看:cached_prompt_text_tokens 表示有多少提示 token 是从缓存取的而非重算,prompt_image_tokens 表示视觉内容产生的 token,与文本 token 分开计数,没有图像或视频时为零。官方也点明了,agentic 请求虽然 prompt_tokens 高,但因为各步之间提示的大部分不变,恰恰非常吃提示缓存的红利。
想给这类请求上一道闸,可以用 max_turns 参数。注意它限制的是 agent 循环里的助手轮数,不是工具调用的个数——一轮里模型可能并行调用多个工具。不指定时服务端会套用一个全局默认上限,达到上限后 agent 停止继续调用工具,基于已有信息直接给出最终回答(默认行为以官方文档当前版本为准)。
缓存维度:先确认命中,再谈省钱
缓存是最直接的成本杠杆,但要先能看见它。官方文档给的字段位置在两套接口里不同,这点很容易搞混:
- Chat Completions API:
usage.prompt_tokens_details.cached_tokens - Responses API:
usage.input_tokens_details.cached_tokens
判读规则官方列了一张表:值为 0 是完全未命中,整个提示从头算,首次请求或缓存被淘汰后属于正常;大于 0 是命中,数字表示被复用的 token 数;等于 prompt_tokens 是全量命中,官方说这种情况少见,通常出现在重复发送完全相同的请求时。
如果同一段会话里多次请求 cached_tokens 一直是 0,官方给的排查方向是两条:确认你有没有设置 x-grok-conv-id(或 prompt_cache_key),以及确认你没有在请求之间修改更早的那些消息。缓存的具体计费口径与命中条件在 Grok 缓存计费机制 里展开更细。
还有一条口径要提前记住,否则容易在限流上翻车:官方 Rate Limits 页写明,缓存的提示 token 仍然计入 TPM,尽管它们按更低的费率计费。也就是说缓存能省钱,但不能帮你绕开每分钟 token 上限,做并发规划时别按「缓存部分不算数」来估。
第二层:控制台里按维度切
请求层的数据是你自己记的,团队层的官方视图是 xAI Console 的 Usage Explorer。官方文档描述的能力有三块:
粒度(Granularity):官方文档演示的是用 Granularity: Daily 按天查看信用消费曲线,页面上其余可选粒度以控制台实际下拉项为准——官方文档里没有列出 Daily 之外的取值,别照着猜。这一层决定的是曲线的横轴密度:按天看适合发现「某天突然翘起来」,真要定位到具体某一批调用,还得靠下面的分组与过滤两层收窄。
维度(Dimension):默认按消费金额统计,可以切换成 Tokens 看 token 数,或者切换成 Billing items 看计费项数量。这三个视角回答的是不同问题——金额看总盘,token 看用量结构,计费项看有没有某类调用异常放量。
分组(Group by):按 API Key 分组是最实用的一个,官方举的例子正是拿它来对比测试与生产两把 key 的消耗。这也反过来给了一条工程建议:如果你所有环境共用一把 key,这一层维度就永远拆不出来。
过滤(Filters):官方列出的可过滤条件包括某一把 API Key、某个模型、某个请求来源 IP、某个集群,以及 token 类型。请求 IP 这个维度不太常见,排查「是不是某台机器上的脚本跑飞了」时很好用。
第三层:用 Management API 拉进自己的看板
要做自动化告警或者把成本并进内部 BI,就得用 Management API。这里有两个前置条件必须先说清楚,官方 Management API 指南里都写了:
- 管理密钥(management key)与推理用的 API Key 是两把不同的钥匙,管理密钥在 Console 的 Settings -> Management Keys 里取。
- 基址也不同,管理接口的 base URL 是
https://management-api.x.ai,不是推理接口那个域名。
用量分析走的是 POST /v1/billing/teams/{team_id}/usage,官方描述是「按时间段获取 API 历史用量,并按字段聚合」。请求体是一个 analyticsRequest 对象,几个字段值得逐个看:
timeRange:含startTime、endTime、timezone三项。时间格式是YYYY-MM-DD HH:MM:SS,endTime不含端点。时区必须用 IANA 时区标识符(例如America/New_York)。官方在这里加了一句解释:因为日志聚合方式的关系,这个接口不能依赖 UTC 时间戳,所以要求你显式给本地时区——这个设计有点反直觉,但知道了就不会填错。timeUnit:时间桶粒度枚举,官方参考里列出的取值依次是TIME_UNIT_INVALID、TIME_UNIT_MONTH、TIME_UNIT_CALENDAR_WEEK、TIME_UNIT_DAY、TIME_UNIT_HOUR、TIME_UNIT_QUARTER_HOUR、TIME_UNIT_MINUTE、TIME_UNIT_SECOND、TIME_UNIT_NONE(以官方文档为准)。其中TIME_UNIT_NONE表示所有事件放进一个桶,适合算「总计」;打头的TIME_UNIT_INVALID是这类 protobuf 风格枚举的零值占位,不要当成一种可用粒度去填。values:要聚合的字段,每项含name和aggregation。聚合方式枚举官方列出的取值是AGGREGATION_NONE、AGGREGATION_SUM、AGGREGATION_AVG、AGGREGATION_VAR、AGGREGATION_STD、AGGREGATION_MIN、AGGREGATION_MAX、AGGREGATION_P50、AGGREGATION_P90、AGGREGATION_P99、AGGREGATION_P999、AGGREGATION_COUNT、AGGREGATION_COUNT_DISTINCT(以官方文档为准)。除了求和、均值这些常规项,方差、标准差和四个分位数都在列,做异常检测时用得上:均值被少数几次巨型请求拉高的时候,P50 和 P99 拉开的距离比平均值更能说明问题。官方提醒了一句:不是每个字段都支持每种聚合方式,所以选定字段之后要以接口实际返回为准,不要假设任意组合都能跑通。groupBy:按元组分组,每个分组值返回一条时间序列。官方给的请求示例里用的是description分组,返回的序列名形如「Chat 加模型名」,也就是说按模型拆消费是开箱可用的。filters:所有过滤条件之间是 AND 关系。
返回体里有个字段一定要读:limitReached。官方说明是,如果它为 true,表示查询的最大基数已经达到,返回的只是结果的一个子集。做定时任务时如果不检查它,你的看板可能长期在用残缺数据画图,而且不会报错——这是那种最难发现的错。
账单层:发票行项和余额变动能拆到什么程度
如果要做到「和财务对账」,还有两个接口。
GET /v1/billing/teams/{team_id}/invoices 列出团队发票,可按账期年月、起始年月或发票 ID 过滤。每张发票的 lines 数组就是账单行项,每行含 clusterName(资源消耗所在集群)、description(行项描述)、unitType(计价单位,例如提示文本 token、补全文本 token)、unitPrice、numUnits、amount。发票状态 invoiceStatus 是枚举,官方列出的取值是 INVALID、PENDING、PAID、WILL_NEVER_BE_CHARGED、FAILED(以官方文档为准)。对账脚本要留意 WILL_NEVER_BE_CHARGED 这一项:它既不是待扣款也不是扣款失败,如果按「非 PAID 即欠费」的二分法写判断,这类发票会被错误地算进未结清金额里。此外还有 chargerAttempts 记录每次扣款尝试是否成功。
GET /v1/billing/teams/{team_id}/prepaid/balance 列出预付信用余额和余额变动。这里的 changeOrigin 枚举很有用,官方列出的取值是 INVALID_ORIGIN、PURCHASE、SPEND、REFUND、MANUAL、AUTO_PURCHASE(以官方文档为准),其中后五项连正负号约定都写了:PURCHASE(用户购买,金额为负)、SPEND(用户消费,金额为正)、REFUND(退款,金额为负)、MANUAL(xAI 员工操作,可正可负)、AUTO_PURCHASE(只能为负)。打头的 INVALID_ORIGIN 同样是枚举零值占位,官方没有给它配正负号说明。第一次看到「购买是负数」会愣一下,但只要理解成「这是对余额扣减方向的记账」就顺了。做对账脚本时,符号搞反会让整张表偏一倍。
付款与地区:只说官方写了什么
官方 Billing FAQ 里明确写了一条:目前无法处理印度发行的支付卡用于 API 服务,官方表示正在推进支持,并建议在此期间考虑其他厂商提供的 API(官方原文用词是 third-party API,指的是改用别家平台的接口,不是任何形式的代充值或转售渠道);Grok 网站与 App 的支付走的是另一套,不受影响。另外,预付信用的购买目前只能走 Guest Checkout,官方给的理由是监管要求;银行转账购买需要几个工作日处理,到账后才发放信用。
关于中国大陆的可用性,xAI 官方文档里没有找到相关说明。国内团队如果有明确的合规与可控性要求,更稳妥的做法是评估国内厂商的 API,这方面可以参考 国内 API 可达性与合规。
最后:三个最容易栽的坑
第一,预付信用耗尽会直接拒绝请求。官方文档写得很清楚:在未提高开票计费额度的默认状态下,xAI 只使用你的预付信用,一旦用尽,API 请求会被自动拒绝。要避免服务中断,要么开启自动充值,要么提高开票额度(后者需要联系官方开通,默认是关闭的)。控制台会在接近你设定的月度上限、以及当日自动充值次数快用完时给出提醒,具体阈值以官方文档和控制台显示为准。
第二,token 数和 tokenizer 算出来的对不上是正常的。官方 FAQ 解释了原因:推理端点会加入一些预定义 token 来处理请求,这些也计入总的提示 token 消耗。所以拿 tokenizer 预估月成本时要留出这部分余量,别把预估当上限用。
第三,批量任务的成本要看批次对象自己的字段。官方 Batch API 文档说明,批次结果里每条请求都有各自的成本,你可以逐条求和,也可以直接读批次对象上的 cost_breakdown。批量接口按低于标准价计费,具体比例见官方定价页。这条链路的其他细节见 Grok Batch API 怎么用。
真要落地,我的建议是从最小可用开始:先在你的 HTTP 客户端或 SDK 封装里,把 cost_in_usd_ticks、模型名、API Key 标识、是否命中缓存这四项一起写进日志。有了这四列,后面所有的维度拆分都是 SQL 的事;没有它们,你就只能一直盯着控制台的曲线猜。