UniGateway 用量详情查询 API
通过一次推理请求的请求 ID 查询该请求的用量、实际费用和计费状态。接口返回单个 JSON 对象,适用于请求级对账、异常排查和在业务系统中展示实际计费结果。
准备工作
请前往 UniGateway API Keys 创建或获取 API Key。
请仅在服务端环境变量或受控的密钥管理服务中保存 API Key。不要将完整 API Key 写入浏览器前端代码、日志、公开仓库或工单附件。
接口说明
| 项目 | 值 |
|---|---|
| 方法 | GET |
| 路径 | /v1/usage/{request_id} |
| API 地址 | https://unigateway.ai/v1/usage/{request_id} |
| 鉴权 | Authorization: Bearer $UNIGATEWAY_API_KEY |
| 响应格式 | application/json |
调用时必须使用产生该推理请求的同一个 API Key。接口不会返回其他 API Key 的记录,也不使用网页登录会话鉴权。请求没有请求体,也不需要查询参数。
路径参数
| 名称 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
request_id | Path | string | 是 | 推理响应头 x-oneapi-request-id 的原始值。长度为 1 至 64,仅允许英文字母、数字、下划线和连字符:[A-Za-z0-9_-]{1,64}。 |
request_id 不是用量列表记录的 id,也不是推理响应体中的 id、内部 trace ID 或上游请求 ID。
调用示例
先从任意 UniGateway 推理接口的响应头取得请求 ID。以下列出常见请求类型及接口示例:
| 请求类型 | 示例接口 |
|---|---|
| 文本生成与对话 | /v1/chat/completions、/v1/responses |
| 图像生成与编辑 | /v1/images/generations、/v1/images/edits |
| 视频生成 | /api/v3/contents/generations/tasks(创建视频任务) |
以上仅为示例,并非完整接口列表。本接口支持查询所有 UniGateway 推理接口产生的用量记录,不限于上述接口。获取请求 ID 后,是否已有可查询的消费记录,以本接口返回的状态码为准。
响应头示例如下:
x-oneapi-request-id: {request_id}
cURL 请求示例
再使用同一个 API Key 查询:
curl -i 'https://unigateway.ai/v1/usage/{request_id}' \
-H "Authorization: Bearer $UNIGATEWAY_API_KEY"
将 {request_id} 替换为目标推理请求响应头中返回的 x-oneapi-request-id 原始值。
在 Windows PowerShell 中,请使用 curl.exe,避免 curl 别名改变参数行为。
以下 Python 和 JavaScript 示例从服务端环境变量 UNIGATEWAY_API_KEY 读取 API Key。执行前,将 {request_id} 替换为推理响应头中的实际请求 ID。每次查询的超时时间为 30 秒,最多查询 5 次;仅在返回 202 时按 Retry-After 等待后重新查询,其他错误停止查询并输出错误信息。
Python 请求示例
示例使用 Python 标准库,无需安装额外依赖。
import json
import os
import time
from urllib.error import HTTPError
from urllib.request import Request, urlopen
def get_usage(request_id):
request = Request(
f"https://unigateway.ai/v1/usage/{request_id}",
headers={"Authorization": f"Bearer {os.environ['UNIGATEWAY_API_KEY']}"},
)
for attempt in range(5):
try:
with urlopen(request, timeout=30) as response:
status = response.status
retry_after = response.headers.get("Retry-After", "1")
data = json.load(response)
except HTTPError as error:
body = error.read().decode("utf-8", errors="replace")
raise RuntimeError(
f"Usage query failed (HTTP {error.code}): {body}"
) from error
if status == 200:
return data
if status != 202:
raise RuntimeError(f"Unexpected HTTP status: {status}")
if attempt < 4:
time.sleep(max(1, int(retry_after)))
raise TimeoutError("Usage is still pending after 5 queries.")
usage = get_usage("{request_id}")
print(json.dumps(usage, ensure_ascii=False, indent=2))
JavaScript 请求示例
示例适用于 Node.js 18 或更高版本,使用内置 fetch,无需安装额外依赖。
async function getUsage(requestId) {
const apiKey = process.env.UNIGATEWAY_API_KEY;
if (!apiKey) {
throw new Error("UNIGATEWAY_API_KEY is not set.");
}
for (let attempt = 0; attempt < 5; attempt++) {
const response = await fetch(
`https://unigateway.ai/v1/usage/${requestId}`,
{
headers: { Authorization: `Bearer ${apiKey}` },
signal: AbortSignal.timeout(30_000),
},
);
const body = await response.text();
if (response.status === 200) {
return JSON.parse(body);
}
if (response.status !== 202) {
throw new Error(
`Usage query failed (HTTP ${response.status}): ${body}`,
);
}
if (attempt < 4) {
const retryAfter = Number(response.headers.get("Retry-After") ?? "1");
const delaySeconds = Number.isFinite(retryAfter)
? Math.max(1, retryAfter)
: 1;
await new Promise((resolve) => setTimeout(resolve, delaySeconds * 1000));
}
}
throw new Error("Usage is still pending after 5 queries.");
}
async function main() {
const usage = await getUsage("{request_id}");
console.log(JSON.stringify(usage, null, 2));
}
main().catch((error) => {
console.error(error.message);
process.exitCode = 1;
});
成功响应
请求成功时返回 HTTP 200 和单个用量记录。以下为 gpt-image-2 图像生成请求的用量详情示例,记录 ID、API Key 标识和名称已替换为示例值,请求 ID 使用 {request_id} 占位符。示例金额仅对应该条记录,不代表 model 的当前价格;model ID、能力、价格和可用性以当前账户的控制台、模型库或接口返回为准。
{
"id": "usage_example_001",
"token_id": "token_example_001",
"token_name": "example-key",
"model_id": "gpt-image-2",
"model_name": "gpt-image-2",
"prompt_tokens": 10,
"completion_tokens": 201,
"cache_read_tokens": 0,
"cache_write_tokens": 0,
"total_tokens": 211,
"request_count": 1,
"duration_seconds": null,
"billable_duration_seconds": null,
"wan3_usage": null,
"total_duration_seconds": 28,
"first_token_latency_ms": 27906,
"is_stream": true,
"size_multiplier": null,
"billing_mode": "PER_TOKEN",
"original_price": "0.000050000000",
"final_price": "0.000050000000",
"savings_amount": "0.000000000000",
"discount_percentage": null,
"discount_source": "NONE",
"cache_read_original_price": "0",
"cache_write_original_price": "0",
"cache_read_final_price": "0",
"cache_write_final_price": "0",
"cache_creation_tokens_5m": 0,
"cache_creation_tokens_1h": 0,
"tier_triggered": false,
"applied_context_tier_mode": "DISABLED",
"applied_tier": 1,
"tier_threshold_tokens": null,
"input_price_per_million_applied": "5.000000000000",
"output_price_per_million_applied": "0.000000000000",
"cache_read_price_per_million_applied": "0.000000000000",
"cache_write_price_per_million_applied": "0.000000000000",
"cache_write_breakdown": {
"cacheCreationTokens5m": 0,
"cacheCreationTokens1h": 0,
"aggregateCacheWriteTokens": 0,
"cacheWrite5mPricePerMillionApplied": null,
"cacheWrite1hPricePerMillionApplied": null,
"cacheWrite5mOriginalPrice": null,
"cacheWrite1hOriginalPrice": null,
"cacheWrite5mFinalPrice": null,
"cacheWrite1hFinalPrice": null
},
"media_pricing_rule": null,
"seedance_media_pricing": null,
"media_usage": {
"modality": "image",
"resolution": null,
"size": null,
"quality": "auto",
"imageCount": 1,
"imageOutputUnits": 201,
"requestPath": "/v1/images/generations",
"ratio": null,
"duration": null,
"inputMediaCount": null,
"inputImageCount": null,
"inputVideoCount": null,
"inputHasVideo": null,
"outputHasAudio": null,
"inferenceMode": null,
"draft": null,
"framesPerSecond": null,
"inputVideoDurationSeconds": null
},
"billing_detail": {
"originalPrice": "0.00005",
"finalPrice": "0.00005",
"discountApplied": {
"source": "NONE",
"percentage": "0"
}
},
"currency": "USD",
"settlement_currency": "USD",
"exchange_rate": "1",
"status": "SUCCESS",
"error_reason": null,
"error_reason_message": null,
"error_reason_action": null,
"created_at": "2026-10-12T07:04:53.000Z",
"request_id": "{request_id}"
}
主要返回字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 用量记录 ID,与请求 ID 不同。 |
request_id | string | 本次查询使用的请求 ID。 |
token_id / token_name | string / string 或 null | 当前 API Key 的 ID 和名称。 |
model_id / model_name | string 或 null / string | model ID 和本次调用记录中的 model 名称。 |
prompt_tokens / completion_tokens | number | 输入和输出 token 用量。 |
cache_read_tokens / cache_write_tokens | number | 缓存读取和写入 token 用量。 |
total_tokens | number | 记录中的总 token 数。不要自行将缓存 token 再次相加推导。 |
request_count | number | 计费请求数。 |
is_stream | boolean | 是否为流式请求。 |
billing_mode | string | PER_TOKEN、PER_REQUEST 或 PER_SECOND;以当前记录为准。 |
original_price | string | 折扣前金额。 |
discount_percentage / discount_source | string 或 null | 折扣百分比和折扣来源;例如 "20" 表示优惠 20%。 |
savings_amount | string | 节省金额。 |
final_price | string | 实际计费金额。 |
settlement_currency | string | 金额结算币种,当前固定为 USD。 |
currency / exchange_rate | string / string | model 原始定价币种,以及换算至结算币种时使用的汇率。 |
status | string | SUCCESS、PARTIAL、FAILED、UNKNOWN_MODEL 或 REFUNDED。 |
error_reason | string 或 null | 异常原因标识。 |
error_reason_message / error_reason_action | string 或 null | 异常说明及处理提示。 |
created_at | string | ISO 8601 格式的记录时间;末尾 Z 表示 UTC。 |
金额字段使用十进制字符串。original_price、final_price 和 savings_amount 固定保留 12 位小数;请使用高精度十进制类型处理金额,避免二进制浮点误差。
扩展字段
扩展字段用于说明请求耗时、缓存费用、阶梯价格和媒体用量。字段返回 null 时,表示该项不适用于本次请求或记录中没有对应数据;数值 0 表示该项记录的值为零。
- 耗时与计费量:
duration_seconds、billable_duration_seconds、total_duration_seconds、first_token_latency_ms、size_multiplier。 - 缓存费用:
cache_read_original_price、cache_write_original_price、cache_read_final_price、cache_write_final_price、cache_creation_tokens_5m、cache_creation_tokens_1h、cache_write_breakdown。 - 阶梯与已应用单价:
tier_triggered、applied_context_tier_mode、applied_tier、tier_threshold_tokens、input_price_per_million_applied、output_price_per_million_applied、cache_read_price_per_million_applied、cache_write_price_per_million_applied。 - 媒体与计费详情:
media_usage、media_pricing_rule、seedance_media_pricing、wan3_usage、billing_detail。
图像用量字段
图像生成与编辑请求通过 media_usage 返回媒体用量和请求规格。上方示例中的主要字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
media_usage.modality | string | 媒体类型;图像请求为 image。 |
media_usage.resolution | string 或 null | 记录中的输出分辨率。 |
media_usage.size | string 或 null | 记录中的输出尺寸。 |
media_usage.quality | string 或 null | 记录中的图像质量参数,示例为 auto。 |
media_usage.imageCount | number 或 null | 图像数量。 |
media_usage.imageOutputUnits | number 或 null | 图像输出用量,示例为 201;不是图像数量。 |
media_usage.requestPath | string | 本次图像请求的接口路径,示例为 /v1/images/generations。 |
billing_detail 返回该记录的金额和折扣明细:
| 字段 | 类型 | 说明 |
|---|---|---|
billing_detail.originalPrice | string | 折扣前金额,与顶层 original_price 表示相同金额,小数位数可能不同。 |
billing_detail.finalPrice | string | 实际计费金额,与顶层 final_price 表示相同金额,小数位数可能不同。 |
billing_detail.discountApplied.source | string | 折扣来源;示例中的 NONE 表示未应用折扣。 |
billing_detail.discountApplied.percentage | string | 已应用的折扣百分比;示例中的 "0" 表示未应用折扣。 |
状态码与错误处理
非 200 响应使用统一的 error 对象;202 表示记录仍在同步中。
{
"error": {
"code": "USAGE_PENDING",
"message": "Usage record is not yet synchronized",
"param": null,
"type": "Accepted"
}
}
404 也可能表示请求尚未结束、消费记录尚未生成、记录已清理或无法关联到当前 API Key,不能据此断定推理请求失败。其他 API Key 的请求同样返回 404,接口不会泄露记录是否存在。获取到请求 ID 响应头也不保证一定有可查询的消费记录。
使用注意事项
- 本接口与
GET /v1/usage共用每个 API Key 每分钟 120 次的查询额度。轮询时应设置最大尝试次数或超时上限。 - 建议在推理请求结束后查询。收到
202时按Retry-After重试,不要把待同步状态当作零费用或成功详情。 - 接口返回当前 API Key 对应的单条消费记录及其计费状态,包括失败、model 未匹配或已退款状态,不受用量列表接口默认
charged_only筛选限制。 - 查询为只读操作,不触发同步或扣费;所有响应均带有
Cache-Control: no-store。 - API Key 重新分配给子账户不会重写历史用量归属。持有同一有效 API Key 的调用方仍可按该 Key 查询历史记录。
常见问题
Q: 应使用哪个 ID 查询用量?返回 400 时如何检查?
使用目标推理请求响应头 x-oneapi-request-id 的原始值,并替换查询路径中的 {request_id}。不要使用推理响应体的 id、用量记录的 id 或视频任务 ID。返回 400 时,检查请求 ID 是否为 1 至 64 个字符,且仅包含英文字母、数字、下划线和连字符;移除误复制的空格、引号或占位符大括号,再使用同一个 API Key 重新查询。
Q: 返回 202 USAGE_PENDING 时,是否表示没有产生费用?
不是。202 表示消费日志已存在,但用量详情尚未同步,不能将其解释为零费用。读取响应头 Retry-After 并在指定时间后重新查询;当前返回 Retry-After: 1,应至少等待 1 秒。本文 Python 和 JavaScript 示例最多查询 5 次;达到上限后停止本轮查询,稍后再核对,不要无限轮询。
Q: 返回 404 是否表示推理请求失败?
不是。先确认请求 ID 来自目标推理请求的 x-oneapi-request-id 响应头,并确认查询使用的是发起该请求的同一个 API Key。请求尚未结束或消费记录尚未生成时,可在请求结束后重新查询;记录已清理或无法关联到当前 API Key 时,也可能返回 404。推理结果应以原请求响应或异步任务状态为准,不要仅凭用量查询的 404 判断成功或失败。
Q: 返回 401 或 403 时如何处理?能否换一个 API Key 查询?
检查服务端环境变量 UNIGATEWAY_API_KEY 和请求头 Authorization: Bearer $UNIGATEWAY_API_KEY,确认没有遗漏 Bearer 前缀,且变量值为发起推理请求的同一个有效 API Key。在 UniGateway API Keys 检查该 Key 的状态和访问控制;返回 403 时,还需确认其所属工作区或子账户可用。修正配置后重新加载或重启调用服务,再查询同一请求 ID。新建或更换为其他 API Key 不能查询原 Key 的记录。
Q: 视频生成应使用任务 ID 还是请求 ID?何时查询用量?
使用创建视频任务请求响应头中的 x-oneapi-request-id,不要将响应体中的任务 ID 填入 /v1/usage/{request_id}。视频生成是异步任务,创建成功不表示生成和计费已完成;先通过任务查询接口核对任务是否已结束,再使用创建请求的请求 ID 和同一个 API Key 查询用量。返回 202 时按 Retry-After 等待后重试;返回 404 时按上方检查步骤核对,不要据此判断视频任务失败。
Q: 如何核对实际费用?失败请求是否一定免费?
以返回记录的 final_price 和 settlement_currency 核对实际计费金额,同时查看 billing_mode、status 及适用的缓存、媒体和折扣明细。失败或部分完成的请求也可能产生用量,不要仅根据状态或总 token 数推断费用。金额使用十进制字符串;例如 "0.000050000000" 与 billing_detail.finalPrice 中的 "0.00005" 表示相同金额。发现差异时,使用同一个 API Key 和请求 ID 重新查询,并保留记录 ID、查询时间及错误说明以便排查。
Q: 图像返回中的 imageOutputUnits 是图像数量吗?null 是否等于零?
不是。图像数量查看 media_usage.imageCount,图像输出用量查看 media_usage.imageOutputUnits;本文示例表示 1 张图像、201 个图像输出用量单位。null 表示字段不适用或记录中没有对应数据,不等同于数值 0。核对图像请求时,同时检查 media_usage.requestPath、quality 和实际费用;size 或 resolution 为 null 不能据此判断图像生成失败,生成结果应以原请求响应为准。
Q: 返回 429 时如何调整查询频率?
本接口与用量列表接口 GET /v1/usage 共用每个 API Key 每分钟 120 次的查询额度。检查同一 API Key 下所有服务的轮询和批量查询任务,降低并发、延长查询间隔,等待后再重试;持续返回 429 时,逐步延长重试间隔。设置最大尝试次数或超时上限,不要通过立即重试增加请求频率。
Q: 返回 409 USAGE_AMBIGUOUS 时如何处理?
确认请求 ID 为原响应头 x-oneapi-request-id 的完整值,并确认使用的是发起该请求的 API Key,再重新查询。该错误表示同一请求 ID 在当前 API Key 下匹配到多条消费日志,接口不会任意返回其中一条。持续返回 409 时,请向支持提供请求 ID、请求时间、model、错误响应和脱敏后的 API Key 名称或标识,不要提供完整 API Key,也不要自行选择某条记录作为计费结果。