生命周期概览
- Mint:使用普通密钥创建预算子密钥。保存子密钥
id、业务ref和仅返回一次的 secret。 - 调用:在到期前,使用子密钥 secret 发起一次或多次 LLM 调用,显式指定输出上限并设置 call tag。
- 查看:使用普通密钥读取实时支出和 call receipt。
- 关闭:turn 结束后关闭子密钥。轮询直到
status: closed,再根据最终spent_usd结算用户的 authorization hold。
https://api.sunra.ai 和 Authorization: Key \$SUNRA_KEY,其中 SUNRA_KEY 必须是有效的普通密钥。预算子密钥不能调用管理端点。参见鉴权。
Media endpoints
媒体访问启用后,预算池子密钥可调用所有多模态队列端点及上传初始化,仍须遵守密钥自身的模型访问限制。通过https://api.sunra.ai 使用 Authorization: Bearer <child secret> 调用:
不入池的 standalone 预算子密钥调用这些路由返回
403 budget_key_route_forbidden。上传初始化不计费,返回 201;使用响应中的签名上传 URL 传输文件。
每个媒体提交与 organization 额度冻结使用同一准入金额:pre_defined 端点按精确价,post_calculated 端点按价卡的 freeze_amount 冻结额。例如 seed-asr-2.0 为 18–21,seedance-2.0 为 1 cap 可先持有 2;池总授权足够时仍 active,子密钥 spent_usd 记录 $2,下一次提交被拒绝。
402 准入响应中的 estimate_usd 给出该单所需的准入金额。同价的 N 个并发提交在结算释放持有前需要 N 倍可用授权余量;混合端点则需要各单准入金额之和。1.00 的音频持有;N 个每单冻结 21 余量。实际费用较低时释放差额。失败调用若已收费,即使发票为 VOID 也会记录实收。
x-sunra-call-tag 只在提交 prediction 时校验。配置了 metadata.allowed_tags 的子密钥在提交时必须携带允许的 tag;上传、状态、结果、流与取消不要求 tag。子密钥只能读取或取消自己提交的 request;他人或不存在的 request 均返回 404,流接口也返回 HTTP 404。
关闭或撤销子密钥不会取消在途任务。池子密钥 close 返回 HTTP 200,在发票结算前 body 为 status: "finalizing",结算后为 closed;终值包含晚到的媒体收费。
/calls 先按数据库 ID 顺序列出 LLM 行,再列出媒体行。cursor 为调用 ID(chatcmpl_ 或 pd_)。open 列表不是快照:cursor 进入媒体段后新增的 LLM 行不会出现在本轮遍历中。完整账本应在 close 后从头读取,并确认 complete: true;calls_count 合计两类调用。媒体 receipt 含 endpoint、非 token 的 usage: { usage_key, usage, unit_amount }、提交 tag 与实际 cost_usd,不含 wire_id。 媒体 usage 缺失字段显式为 null:无 usage 数据的取消单或 charged-VOID 单返回 usage: { usage_key: null, usage: null, unit_amount: null },close 后保持一致;媒体 receipt 不含 token 计数器。
创建预算子密钥
向POST /v1/budget-keys 发送 JSON body:
可选字段请直接省略,不要发送
null。未知的顶层字段会被拒绝。省略 models 时,子密钥继承父密钥的模型限制;设置后,每次调用都必须同时满足该列表和父密钥当前的 allowlist。
以下示例使用 jq 将到期时间设为一小时后,并创建上限为 $0.05 的密钥:
201 response 直接返回资源,不包含 data 包装层。示例值如下:
secret_key:mint 仅返回一次。 GET、list、receipt 和 close 均不会返回它。Mint 不具备幂等性:重复请求即使使用相同的 ref,也会创建不同的子密钥。如果 response 丢失,请先使用下文的恢复端点找到并关闭遗留密钥,再决定是否重新 mint。
使用子密钥调用 LLM API
使用https://api-llm.sunra.ai 和 Authorization: Bearer <child secret>。仅支持以下端点:
除上述池子密钥媒体路由外,其他 API 操作,包括预算子密钥管理和 native/media embeddings,均以
403 budget_key_route_forbidden 拒绝子密钥。已过期或已撤销的凭证可能先在鉴权阶段失败;无法识别的 URL 仍可能返回 404。
使用 Chat Completions 时,每次子密钥请求都必须发送 max_tokens 或 max_completion_tokens,并选择模型支持的字段。两者都缺失时返回 400 max_tokens_required。值必须是正的 safe integer;如果同时提供两个字段,它们必须相等。Messages 使用 max_tokens,Responses 使用 max_output_tokens。后两种 API 可以根据模型配置的输出上限补充缺失值;显式发送上限可以明确本次调用的输出额度。
准入估算对每个图片、音频、视频或文档 part 预留 8,192 tokens。如果模型对该媒体类型单独计价,则使用对应的输入费率;否则将这些 tokens 折入 prompt_tokens,按 prompt/input 费率预留。这是估算约定,不是 provider 强制保证的上界。结算仍使用实际 usage。
文本部分按序列化后的整个请求(含 tools 与 system)保守估算,因此纯文本调用的实际结算几乎总是低于预留。已知只有三种情况实际结算会超过预留,请按您实际会遇到的情况确定 hold 余量:Anthropic 格式请求的 prompt cache 写入按缓存写入费率结算(输入费率的 1.25×,ttl 为 1 小时时为 2×),预留不包含这部分溢价(带 tool search 的 Claude 模型即使不写 cache_control 也可能写入缓存前缀);单个媒体 part 超过 8,192 tokens 的预留值,长音频、视频或多页文档较常见;以及 xAI Grok 模型,其推理 tokens 不受 max_tokens 约束——这类调用没有可靠上界,如果上限必须成立,请用 models 排除它们。
n 可以省略,但提供时必须为数字 1。best_of 即使为 1 也会被拒绝。
子密钥请求只能携带下列 portable 生成参数,以及所调用端点对应的输出上限字段。其他字段——provider extension bag 和其他写法的 ceiling,例如 extra_body、generation_config、generationConfig、max_new_tokens——一律返回 400 invalid_input:它们可能在转换后放大真实输出上限,使 reservation 失效。允许的顶层字段为:model, provider, stream, stream_options, messages, input, system, instructions, tools, tool_choice, parallel_tool_calls, response_format, text, temperature, top_p, top_k, stop, stop_sequences, seed, presence_penalty, frequency_penalty, logit_bias, logprobs, top_logprobs, user, metadata, store, previous_response_id, include, truncation, reasoning, reasoning_effort, thinking, service_tier, safety_identifier, prompt_cache_key, prompt_cache_retention, verbosity, n, encoding_format, dimensions。使用普通密钥发起的请求不受此限制。
推理预算必须低于输出上限
thinking 和 reasoning 若出现,必须是对象。thinking.type 必须严格为 "enabled" 或 "disabled";adaptive、未知、缺失或非字符串类型对所有子密钥一律拒绝。启用 thinking 时必须提供 thinking.budget_tokens。任何 thinking.budget_tokens 或 reasoning.max_tokens 都必须是正的 safe integer,且严格小于准入的输出上限;等于或超过上限,或格式错误(null、字符串、小数、零、负数、非 safe integer)均在 reservation、冻结额度、调用记录和上游请求之前返回 400 invalid_input。格式错误的值不会被当作”未传”。通过校验的预算原样转发。
对 Claude 模型(含别名)和 Messages 请求,比较时至少按 provider 的 1,024-token 最小推理配额计算,因此 512 的 thinking 预算按 1,024 处理。例如 max_tokens: 64 搭配 thinking: {"type":"enabled","budget_tokens":1024} 会返回 400 invalid_input(thinking.budget_tokens must be below the output ceiling);要使用该预算,请把输出上限设为至少 1,025 并为子密钥准备相应额度。effort 类控制(reasoning_effort、reasoning.effort、reasoning: {"enabled": true})仍然支持,且不会突破上限;thinking: {"type":"disabled"} 仍然支持,包括下文的 Qwen 转换。普通密钥和 session 不受影响。
使用 x-sunra-call-tag 标记调用,值最多为 128 个 printable ASCII 字符。它会作为 tag 出现在 receipt 中。如果密钥设置了 metadata.allowed_tags,缺失 tag 或 tag 不在集合内会返回 403 budget_call_tag_forbidden;否则该 header 为可选。普通密钥完全忽略该 header。
将返回的 secret 赋给 CHILD。-i 会显示 response header:
x-sunra-prediction-id,用于将本次调用关联到对应的 receipt。Receipt 的 id 与该 header 的值逐字节一致;不要用 provider response ID 替代它。
关闭 Qwen Chat 的 thinking
在已映射的 Qwen → DashScope 路由上,子密钥的 Chat Completions 请求可使用thinking: {"type":"disabled"}、reasoning_effort: "none",或同时使用两者。Sunra 会移除这些控制字段并发送原生关闭开关,保留输出上限和 n。子密钥必须允许所选模型和 tag。
qwen/qwen3.7-flash、qwen/qwen3.7-plus、qwen/qwen3.7-max 和 qwen/qwen3.8-max,分别经 LiteLLM 解析到同名的 DashScope group(去掉 qwen/ 前缀)。是否支持取决于实际解析的路由;仅指定模型名称或 provider 偏好不能保证支持。
以下情况会在创建调用记录、冻结 credit 或预留预算之前返回 400 invalid_input:
- 关闭意图与非
"none"的reasoning_effort、不完全等于{"type":"disabled"}的thinking,或任意reasoning字段同时出现。 - disabled
thinking对象包含额外字段,例如budget_tokens。 - 在未映射的 Qwen 路由上请求关闭,或在 Qwen Messages、Responses 上请求关闭。
- 原生
enable_thinking(包括布尔值false)或客户端extra_body:子密钥仍拒绝这两个字段。
usage.reasoning_tokens: 0;字段缺失表示未知。
跟踪支出
读取密钥
将 mint 返回的子密钥id 赋给 ID:
secret_key:
准入会在调用前预留估算售价。实际结算成本可能因在途调用的估算误差而超过上限。请在您自己的 authorization hold 中留出余量;不要根据 open 密钥的支出或 LLM response 已结束就释放 hold。未知的实时值不等于零。
读取逐调用 receipt
limit 默认为 100,允许 1–500。只要 has_more 为 true,就将 next_cursor 作为 after 请求下一页;该值是本页最后一行的 id。请原样传回并进行 URL encoding。无效 cursor 或属于其他子密钥的 cursor 会返回 400 invalid_input。
以下为包含一次已付费调用的密钥在关闭后的示例页面:
PAID、VOID、PENDING 和 UNCOLLECTIBLE 尝试。只有 PAID 行具有结算成本;其他状态的 cost_usd 均为 "0.00000000"。在创建调用记录之前被拒绝的请求不计入。completed_at 和 tag 可以为 null;wire_id 为可选字段。
usage 始终包含四个整数计数:input_tokens、output_tokens、cache_read_tokens 和 cache_write_tokens,不适用时为 0。存在 invoice.price_breakdown 时,计数来自这些计费分桶:input_tokens 仅表示未缓存的输入,三个输入分桶互不重叠,总 prompt tokens = input_tokens + cache_read_tokens + cache_write_tokens。cache_write_tokens 合并 5 分钟和 1 小时的缓存写入层级;output_tokens 为计费输出计数。
每行的 usage_exclusive 表示输入分桶是否互不重叠:用量来自 price_breakdown,或有效的持久化路由 dialect 标记使原始计数可被拆分时,为 true。缺少 price_breakdown 时,receipt 会尽可能读取原始用量中的平面或嵌套 cache 计数,并只减去 dialect 标记为已包含在输入中的分桶,结果最低为 0。若 dialect 标记缺失或格式无效,则保留原始输入计数,usage_exclusive 为 false;此时不能假定上述求和关系成立。实时行使用实时数据;闭票行只使用封存的发票、用量和 dialect 标记。历史封存发票无需迁移即可按 v2 展示。
带有 price_breakdown 的 PAID 行还包含 cost_breakdown,固定为 input_usd、cache_read_usd、cache_write_usd 和 output_usd 四个 8 位小数 USD 字符串,使用 decimal arithmetic 求和时精确等于 cost_usd。每个分桶先以用量 × 单价精确计算,5 分钟和 1 小时写入成本先合并,再使用 ROUND_HALF_EVEN 舍入到 8 位小数。舍入后,将 cost_usd − sum(rounded buckets) 加到精确金额最大的分桶;并列时按 input、cache read、cache write、output 的顺序选择。退款导致 amount_paid 与发票总额不同时,也使用同一规则对齐实际支付金额。非 PAID 行,以及缺少 price_breakdown 的旧 PAID 行,不包含 cost_breakdown。
calls_count 是 receipt 所有分页中的尝试总数,不只是当前页的行数。只有 closed 密钥的 complete 才为 true;open 和 finalizing 状态的 receipt 仍可能变化。最终对账时,请先关闭密钥,从第一页重新读取全部 receipt,并使用 decimal arithmetic 对 status: "PAID" 行的 cost_usd 求和。该总和等于 closed 后的 spent_usd,总行数等于 closed 后的 calls 和 receipt 的 calls_count。仅有 has_more: false 并不表示密钥已关闭。
usage.reasoning_tokens 是从上游用量观测到的可选非负整数。无论网关响应用哪个名字,receipt 里都统一用这一个键:Chat Completions 为 completion_tokens_details.reasoning_tokens,Responses 为 output_tokens_details.reasoning_tokens,Anthropic 形状的 /v1/messages 响应(Claude)为 output_tokens_details.thinking_tokens。明确的 0 表示上游报告了零 reasoning token;缺失表示上游根本没有报告该计数,即未知,不等于零。正数仍是输出 token 的明细,不会再次累加到输出 token 或发票中。实时行读取实时用量;闭票行只读取封存用量,即使实时用量随后变化也不受影响。历史封存记录中已存储的计数也可展示。receipt_version 为 2。
在 dashboard 中查看密钥
platform.sunra.ai/dashboard/budget 的 Budget 标签页在 Keys 下列出组织内的全部预算子密钥,不区分由哪把密钥签发、也不受management 限制。可按 ref / 名称搜索、按状态和父密钥筛选;点开一行可查看逐调用 receipt。该页面只读:mint 与关闭仍由父密钥通过 API 完成。开放中的密钥在可用时显示上文的实时值,已关闭的密钥显示不可变的最终支出与调用次数。预算子密钥不会出现在 API tokens 页面。
关闭密钥
401 budget_key_revoked。Close 不会取消已经准入的调用,它们仍可能继续结算。
Close 具有幂等性。密钥关闭后,重复请求返回相同的最终 snapshot;没有调用的密钥也会返回支出为零的 snapshot。也可以轮询 GET by ID,但即使
status 为 finalizing,GET 也返回 HTTP 200:请始终检查 body。对于独立预算密钥,GET by ID 和 close 会根据已持久化的结算核对未释放的 hold,因此正常情况下,每次调用的结算均已记录后即可完成 finalization。如果结算始终未到达,该 hold 会在调用准入后 30 分钟到期。如果实时状态丢失,finalization 会等待至 expires_at 加 30 分钟;未知实时值仍为 null,不代表零。封存由读取触发:满足上述条件后,仍需继续轮询 GET 或 close。存储故障或回执分批处理可能需要继续重试。
端到端示例
使用 Bash、支持--fail-with-body 的 curl 和 jq 运行以下示例。按照鉴权中的说明将普通密钥赋给 SUNRA_KEY。示例使用 google/gemini-2.5-flash,父密钥必须允许该模型。它会创建一把上限为 $0.05、有效期为一小时的密钥,发起一次调用、读取 receipt、关闭密钥并打印最终支出。Close 前读取的 receipt 仍可能处于 pending 状态。
如果调用或 receipt 读取失败,exit handler 也会尝试关闭密钥。请在应用中保存打印出的 ref 和子密钥 ID,以便在执行中断后恢复处理。
closed 后重新读取全部 receipt 分页。重复发送 LLM 请求不会被去重,可能再次产生费用。
找回丢失的 ticket
如果 mint response 或本地 ticket 记录丢失,可使用已保存的ref 找回子密钥 ID:
{ "data": [...], "has_more": false, "next_cursor": null },data 中包含预算子密钥资源,不含 secret。必须至少提供 ref、parent_api_key_id 或 closed_by_api_key_id 中一个筛选条件。ref 如提供,必须非空且最多 256 个字符;parent_api_key_id 是精确匹配的字符串,长度为 1–128,不做去空格或大小写转换。多个条件同时提供时按 AND 匹配。未知或其他组织的父 ID 返回空列表,不授予任何权限。limit 默认为 100,允许 1–100。如果 has_more 为 true,将不透明的 next_cursor 进行 URL encoding 后作为 after 请求下一页。多个密钥可以共用同一个 ref;请检查所有匹配项并关闭遗留密钥。
List 用于找回密钥。请使用 GET by ID 或 close 获取最终账目状态,不要根据 open 状态的 list 结果结算 authorization hold。Secret 无法找回。
按签发父密钥列出可见子票,无需 ref:
has_more 仅表示还有可见记录。旧的有效 ref 游标可继续使用。游标锚点必须匹配当前组织、筛选条件与可见性;不匹配返回 400 invalid_input,请重新开始扫描。游标不是快照,也不是授权凭证。
管理权限与恢复
所有预算资源响应(mint、get、list 中的每一项和 close)都包含有效的management。该模式同时约束资源读取、calls 回执读取、关闭和列表可见性:
恢复不需要新的 admin key。请使用原组织内另一把 active 普通密钥处理未结 ticket。父密钥查询失败返回可重试的
503 budget_store_unavailable,绝不视为已撤销;父密钥重新激活后,后续请求再次受限。撤销父密钥阻止新的子密钥调用,但不会取消已准入的调用或自动封存回执。
这只是预算管理授权收窄,不是租户隔离。同组织普通密钥仍可通过 /v1/completions 查看子票调用与费用。独立的对账密钥在父密钥有效时不能读取 parent_only 回执;应使用签发父密钥的凭证,或选择 org 模式。需要真正的租户隔离时,应使用独立组织。
parent_only 创建按 rollout 启用;启用前返回 400 budget_key_parent_only_disabled,不要删除 management 后自动重试。筛选和有效模式输出始终提供。运维需先完成全部 auth/apiv1 管理实例的升级并确认索引,再在 auth 设置 BUDGET_KEYS_PARENT_ONLY_ENABLED=true;存在受保护记录后,回滚也必须保留授权检查。
这把密钥做了什么?
分别读取四个来源;不提供聚合/activity 端点。管理读取使用同组织、有效的普通密钥。
预算列表必须至少提供
ref、parent_api_key_id、closed_by_api_key_id 中一个条件,同时提供时按 AND 匹配。关闭者 ID 与父 ID 一样,按 1–128 字符的字符串精确匹配。未知或其他组织的筛选 ID 返回空列表。可选 time_range_start / time_range_end 为带时区的 ISO 日期时间(例如 2026-09-15T12:00:00Z),两端均包含;可以只给一端,起点不得晚于终点。time_field 为 created_at(默认)、close_requested_at 或 closed_at;缺少所选时间戳的记录被排除。按所选时间倒序,再按内部 ID 倒序。无效筛选、时间范围、时间字段或游标返回 400 invalid_input。
每个预算资源包含 created_at、close_requested_at、closed_by_api_key_id;未知值为 null。closed_at 表示最终回执封存时间。封存前首次接受的显式 POST /close 将认证调用者和服务器时间戳与持久化准入撤销原子写入,即使随后返回 503 也保留归因。重试、其他关闭者和终结锁接管都不能覆盖。惰性 GET、calls 触发的终结以及 mint 清理不会填写归因,除非封存前已接受显式关闭。历史 null 表示未知,不能推断父密钥就是关闭者。
撤销响应为 { data: [...], has_more: false }。每项包含 id、object: "api_key_revocation"、api_key_id、revoked_at、revoked_by 和可为 null 的 reactivated_at。存储机制仅保留最近 50 项,一次返回,无游标。目标可以已撤销;同组织的有效普通密钥均可读取。未知或跨组织目标返回 404 api_key_not_found;预算子密钥目标返回 400 invalid_input;子密钥和 session 调用者禁止访问。依赖故障返回 503 budget_store_unavailable,不会伪造空历史。
Actor 结构为 {type: "api_key" | "user" | "system" | "unknown", id: string | null}。api_key / user 的 ID 指向实际执行操作的主体;成员 JWT 撤销记录成员为 user,后端内部撤销记录 system 且 ID 为 null,未知执行者为 unknown 且 ID 为 null。绝不使用密钥创建者代替。已撤销状态下重试不会新增记录;重新激活会在最后一条未结束记录上写入 reactivated_at,再次撤销才追加新记录。已有成员授权的密钥详情 / 列表也返回最新 revoked_at / revoked_by 摘要(重新激活后仍保留)以及 revocation_history。不回填历史撤销:null 摘要或空数组不代表从未撤销。超出最近 50 条的旧记录不再保留,不提供历史完整性时间标记。
各来源保留自己的游标约定:
- 预算列表使用不透明的
after=next_cursor,最多 256 字符;limit为 1–100,默认 100。创建时间分页保留原游标,并按当前筛选和可见性验证锚点。其他时间字段使用版本化游标,绑定组织、调用者、筛选条件、范围和时间字段;修改后必须从第一页重扫,不匹配返回 400。分页不是快照。 - completions 使用
starting_after(最后一行 ID),拒绝ending_before;按内部 ID 倒序,支持包含端点的创建时间范围筛选,limit 默认 10、最大 10000;不能使用预算列表游标。 - 子密钥
/calls使用最后一条 completion 的精确 ID 作为after;limit为 1–500,默认 100。 - revocations 一次返回保留数组,不使用分页游标。
time_field=closed_at 扫描。自身尚未结算的 completion 应按 ID 重读,结算不会改变其创建时间。
最终回执仍是结算权威:关闭后从头读取全部 /calls 页,要求 complete:true,核对仅 PAID 的十进制金额总和及全部尝试次数与最终 spent_usd / calls 一致。活动列表只是观察记录,不是另一套账本。总额已包含子密钥 completion 时,不要再加一次子密钥回执金额。
错误
请同时检查 HTTP status 和error.details.reason;下表说明预算相关错误的处理方式。已有的 model-access、parameter-validation、organization-balance 和 upstream 错误可能保留原有的 response 格式。
402 budget_reserved body 示例:
budget_exhausted 和 budget_reserved 包含准入决策时的全部四个 USD 字符串。对于这些金额字段,budget_state_unknown 只返回 cap_usd;不能将缺失的 spend 和 reservation 值视为零。
限制与保证
- 有效期: 从 mint 校验时起最多 24 小时。到期后停止新调用;最终账目仍需获取 closed snapshot。
- Best-effort cap: 准入预留的是估算售价。实际支出可能因在途调用的估算误差而超过
cap_usd。请在自己的 authorization hold 中预留余量。 - 实时值与最终值: Open 和 finalizing 状态的数值可能变化。只有
status: closed提供权威且不可变的 snapshot;其 receipt 标记为complete: true。Snapshot 覆盖已记录的结算,因此从未记录或在 snapshot 之后才到达的结算可能不包含在最终金额中。 - 计费: 父密钥所属的 organization 仍按原有方式计费,并继续受现有余额和访问限制约束。预算子密钥不会为 wallet 注资,也不会将终端用户的资金转入 Sunra;终端用户的授权和账目由您的应用管理。