API Key 用量查询
通过 API Key 用量查询接口获取用于鉴权的当前 API Key 所产生的调用记录、用量明细和计费结果。该接口适用于按 API Key 对账、成本分析、内部用量展示和自动化导出;不会返回同一账户下其他 API Key 的记录。
接口说明
| 项目 | 值 |
|---|---|
| 方法 | GET |
| 路径 | /v1/usage |
| API 地址 | https://unigateway.ai/v1/usage |
| 鉴权 | Authorization: Bearer $UNIGATEWAY_API_KEY |
| 响应格式 | application/json |
注意:/v1/usage 是 API Key 管理专用接口,必须使用 https://unigateway.ai,不能使用常规的 https://api.unigateway.ai API 地址。
鉴权说明: 此接口使用常规 API Key 鉴权,不使用管理 Key。请将需要查询用量的 API Key 作为 Bearer token;接口仅返回该 API Key 自身产生的调用记录和用量数据。
准备工作
请前往 UniGateway API Keys 创建或获取 API Key。请按业务要求配置 API Key 的访问控制、轮换和安全管理。
将 API Key 配置为服务端环境变量。不要将 API Key 写入浏览器前端代码、日志、公开仓库或导出文件:
export UNIGATEWAY_API_KEY="<YOUR_UNIGATEWAY_API_KEY>"
Windows PowerShell:
$env:UNIGATEWAY_API_KEY = "<YOUR_UNIGATEWAY_API_KEY>"
查询用量
GET /v1/usage
查询参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
limit | integer | 否 | 每页记录数,默认 50,取值范围为 1 至 200。 |
offset | integer | 否 | 偏移量,默认 0,最大 100000。使用 cursor 时必须省略或设为 0。 |
cursor | string | 否 | 上一页返回的 next_cursor。适用于连续拉取和大范围翻页。 |
charged_only | boolean | 否 | 默认 true。未指定 status 时,仅返回 SUCCESS 和 PARTIAL;设为 false 可查询全部状态。 |
status | string | 否 | 精确筛选 SUCCESS、FAILED、PARTIAL、UNKNOWN_MODEL 或 REFUNDED。指定后优先于 charged_only。 |
model_id | string | 否 | 按 model ID 精确筛选。 |
model | string | 否 | 按记录中的 model 名称精确筛选。 |
from | string | 否 | 起始时间,ISO 8601 格式,包含该时间点。 |
to | string | 否 | 结束时间,ISO 8601 格式,包含该时间点。 |
is_stream | boolean | 否 | 传入 true 或 false,按是否为流式请求筛选。 |
from 与 to 例如 2026-07-01T00:00:00Z。时间范围也兼容 start_date / end_date 和 startDate / endDate;新接入优先使用 from 与 to。
cURL 请求示例
以下请求查询指定时间范围内最多 100 条成功记录:
curl -G https://unigateway.ai/v1/usage \
-H "Authorization: Bearer $UNIGATEWAY_API_KEY" \
--data-urlencode "limit=100" \
--data-urlencode "from=2026-07-01T00:00:00Z" \
--data-urlencode "to=2026-07-31T23:59:59Z" \
--data-urlencode "status=SUCCESS"
查询失败、退款等全部状态时,删除 status 并传入 charged_only=false:
curl -G https://unigateway.ai/v1/usage \
-H "Authorization: Bearer $UNIGATEWAY_API_KEY" \
--data-urlencode "limit=100" \
--data-urlencode "charged_only=false"
Python 分页示例
以下示例使用 Python 标准库拉取同一时间范围内的全部记录。金额字段保持为字符串,避免在传输阶段引入浮点精度误差。
import json
import os
from urllib.parse import urlencode
from urllib.request import Request, urlopen
API_URL = "https://unigateway.ai/v1/usage"
params = {
"limit": 100,
"from": "2026-07-01T00:00:00Z",
"to": "2026-07-31T23:59:59Z",
"status": "SUCCESS",
}
cursor = None
while True:
page_params = {**params}
if cursor:
page_params["cursor"] = cursor
request = Request(
f"{API_URL}?{urlencode(page_params)}",
headers={"Authorization": f"Bearer {os.environ['UNIGATEWAY_API_KEY']}"},
)
with urlopen(request) as response:
page = json.load(response)
for record in page["items"]:
print(record["id"], record["model_name"], record["final_price"])
if not page["has_more"]:
break
cursor = page["next_cursor"]
JavaScript 分页示例
以下示例适用于 Node.js 18 或更高版本。不要在浏览器代码中使用该示例,因为 API Key 只能保存在服务端环境中。
const apiUrl = new URL("https://unigateway.ai/v1/usage");
const params = {
limit: "100",
from: "2026-07-01T00:00:00Z",
to: "2026-07-31T23:59:59Z",
status: "SUCCESS",
};
let cursor;
do {
const query = new URLSearchParams(params);
if (cursor) {
query.set("cursor", cursor);
}
apiUrl.search = query.toString();
const response = await fetch(apiUrl, {
headers: {
Authorization: `Bearer ${process.env.UNIGATEWAY_API_KEY}`,
},
});
if (!response.ok) {
throw new Error(`Usage query failed: ${response.status} ${await response.text()}`);
}
const page = await response.json();
for (const record of page.items) {
console.log(record.id, record.model_name, record.final_price);
}
cursor = page.has_more ? page.next_cursor : undefined;
} while (cursor);
响应
响应为 JSON,items 按 created_at 从新到旧排列。不同模型使用相同的列表结构,每次调用对应一条独立记录,不会按模型分组或合并。
下面是包含 Claude、Seedance 和 gpt-image-2 的响应节选。为便于阅读,示例省略了值为 null 的字段和部分通用字段;示例金额仅用于说明响应结构,不代表模型当前价格。
{
"items": [
{
"id": "usage_claude_example",
"model_name": "claude-sonnet-5",
"prompt_tokens": 90,
"completion_tokens": 9,
"cache_read_tokens": 0,
"cache_write_tokens": 124264,
"total_tokens": 99,
"request_count": 1,
"billing_mode": "PER_TOKEN",
"original_price": "0.310930000000",
"discount_percentage": "21",
"savings_amount": "0.065295300000",
"final_price": "0.245634700000",
"settlement_currency": "USD",
"cache_creation_tokens_5m": 124264,
"cache_creation_tokens_1h": 0,
"cache_write_breakdown": {
"cacheCreationTokens5m": 124264,
"cacheCreationTokens1h": 0,
"aggregateCacheWriteTokens": 124264,
"cacheWrite5mPricePerMillionApplied": "2.500000000000",
"cacheWrite1hPricePerMillionApplied": "4.000000000000",
"cacheWrite5mOriginalPrice": "0.310660000000",
"cacheWrite1hOriginalPrice": "0.000000000000",
"cacheWrite5mFinalPrice": "0.245421400000",
"cacheWrite1hFinalPrice": "0.000000000000"
},
"currency": "USD",
"status": "SUCCESS",
"created_at": "2026-07-21T03:12:00.000Z"
},
{
"id": "usage_seedance_example",
"model_name": "doubao-seedance-2-0-260128",
"prompt_tokens": 0,
"completion_tokens": 324900,
"total_tokens": 324900,
"request_count": 1,
"billing_mode": "PER_TOKEN",
"original_price": "2.397762000000",
"discount_percentage": "4.55",
"savings_amount": "0.109098171000",
"final_price": "2.288663829000",
"settlement_currency": "USD",
"media_usage": {
"modality": "video",
"resolution": "720p",
"ratio": "16:9",
"duration": 15
},
"seedance_media_pricing": {
"tokenPricePerMillion": 7.38,
"billableTokens": 324900,
"inferenceMode": "ONLINE",
"inputHasVideo": false,
"inputVideoCount": 0,
"resolution": "720p",
"duration": 15,
"ratio": "16:9"
},
"currency": "USD",
"status": "SUCCESS",
"created_at": "2026-07-21T03:11:00.000Z"
},
{
"id": "usage_image_example",
"model_name": "gpt-image-2",
"prompt_tokens": 6618,
"completion_tokens": 4354,
"total_tokens": 10972,
"request_count": 1,
"billing_mode": "PER_TOKEN",
"original_price": "0.183564000000",
"discount_percentage": "35",
"savings_amount": "0.064247400000",
"final_price": "0.119316600000",
"settlement_currency": "USD",
"media_usage": {
"modality": "image",
"resolution": "1K",
"size": "1024x1024",
"quality": "auto",
"imageCount": 1,
"imageOutputUnits": 4354,
"requestPath": "/v1/images/edits"
},
"currency": "USD",
"status": "SUCCESS",
"created_at": "2026-07-21T03:10:00.000Z"
}
],
"total": 3,
"limit": 50,
"offset": 0,
"has_more": false,
"next_cursor": null
}
顶层字段
| 字段 | 类型 | 说明 |
|---|---|---|
items | array<object> | 当前页的用量记录,按 created_at 从新到旧排列。 |
total | integer / null | offset 分页时符合当前筛选条件的记录总数;cursor 分页时为 null。 |
limit | integer | 当前每页记录数。 |
offset | integer / null | 当前 offset;cursor 分页时为 null。 |
has_more | boolean | 是否还有下一页记录。 |
next_cursor | string / null | 下一页使用的不透明游标;没有下一页时为 null。 |
通用字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 用量记录 ID。 |
token_id / token_name | string / null | 当前 API Key 的 ID 和名称。 |
model_id / model_name | string / null | model ID 和本次调用记录的 model 名称。 |
prompt_tokens / completion_tokens | integer / null | 输入和输出 token 用量。 |
cache_read_tokens / cache_write_tokens | integer / null | 缓存读取和缓存写入 token 用量。 |
total_tokens | integer / null | 记录中的总 token 数。缓存 token 通过独立字段返回,不要自行相加推导。 |
request_count | integer | 计费请求数。 |
duration_seconds / total_duration_seconds | number / null | 时长计费或请求耗时信息。 |
first_token_latency_ms | integer / null | 首 token 延迟,仅在适用时返回。 |
is_stream | boolean / null | 是否为流式请求。 |
billing_mode | string | 本条记录的计费方式:PER_TOKEN、PER_REQUEST 或 PER_SECOND。 |
original_price | string | 折扣前金额。 |
discount_percentage / discount_source | string / null | 折扣百分比和折扣来源。 |
savings_amount | string | 相比原价节省的金额。 |
final_price | string | 实际计费金额。 |
settlement_currency | string | 金额和已应用单价字段使用的结算币种,当前为 USD。 |
currency / exchange_rate | string / null | 模型原始定价币种,以及换算为结算币种时使用的汇率。 |
status / error_reason | string / null | 记录状态和异常原因。 |
created_at | string | ISO 8601 格式的记录时间。 |
original_price、final_price、savings_amount、discount_percentage、exchange_rate 以及缓存价格字段均以十进制字符串返回。金额和已应用单价字段使用 settlement_currency,当前为 USD;currency 保留模型的原始定价币种,exchange_rate 表示从原始定价币种换算到结算币种时使用的汇率。处理这些值时请使用高精度十进制类型,避免浮点精度误差。模型专用对象中的部分计数或单价字段可能是 JSON 数值,例如 seedance_media_pricing.tokenPricePerMillion。
同一模型的 billing_mode 可能因请求类型或计费规则而不同,请始终使用当前记录返回的值,不要根据模型名称推断。
模型专用字段
模型专用字段不适用或数据不可用时,通常返回 null;部分历史记录可能返回只包含 0 或 null 的对象。客户端应先判断对象及所需字段是否有有效值。
| 字段 | 适用场景 | 说明 |
|---|---|---|
cache_creation_tokens_5m | Claude 缓存 | 5 分钟缓存写入 token |
cache_creation_tokens_1h | Claude 缓存 | 1 小时缓存写入 token |
cache_write_breakdown | Claude 缓存 | 5 分钟和 1 小时缓存写入量、单价、原价及实收拆分 |
media_usage | 图像或视频 | 分辨率、尺寸、质量、时长、画幅和媒体数量等本次请求规格 |
media_pricing_rule | 媒体计费 | 本次请求匹配的公开计费条件和有效计费量 |
seedance_media_pricing | Seedance 视频 | Seedance 本次请求的计费单价、有效计费量和媒体条件 |
Claude 记录仍会通过 cache_write_tokens 返回缓存写入总量。如果历史记录无法可靠区分 5 分钟和 1 小时缓存写入,两个拆分字段会返回 null,此时应使用 cache_write_tokens 读取总量。
分页
接口支持 offset 和 cursor 两种分页模式。连续拉取、自动化导出和大范围查询应优先使用 cursor;cursor 不返回或依赖 total、offset,可避免客户端处理大记录总数或大偏移量时的 BigInt 与整数精度问题。
| 模式 | 请求方式 | 适用场景 | 响应特征 |
|---|---|---|---|
| offset | 传入 limit 与 offset。 | 小范围查询、人工指定页码。 | 返回 total 和当前 offset。 |
| cursor | 首次不传 cursor,后续传入上一页 next_cursor。 | 推荐用于连续拉取、批量导出和断点恢复。 | total 和 offset 为 null。 |
两种模式不能混用:使用 cursor 时,必须省略 offset 或将其设为 0。
offset 分页
首次查询使用 offset=0;下一页按 offset + limit 计算。例如,每页 100 条时查询第 3 页:
curl -G https://unigateway.ai/v1/usage \
-H "Authorization: Bearer $UNIGATEWAY_API_KEY" \
--data-urlencode "limit=100" \
--data-urlencode "offset=200" \
--data-urlencode "from=2026-07-01T00:00:00Z" \
--data-urlencode "to=2026-07-31T23:59:59Z"
offset 最大为 100000。查询大范围记录时,不要通过持续增大 offset 翻页,应改用 cursor。
cursor 分页
首次请求不传 cursor。当响应 has_more 为 true 时,保留完全相同的筛选条件,并将 next_cursor 原样传入下一次请求。不要解析、修改或复用其他查询返回的 cursor:
curl -G https://unigateway.ai/v1/usage \
-H "Authorization: Bearer $UNIGATEWAY_API_KEY" \
--data-urlencode "limit=100" \
--data-urlencode "from=2026-07-01T00:00:00Z" \
--data-urlencode "to=2026-07-31T23:59:59Z" \
--data-urlencode "cursor=<NEXT_CURSOR>"
cursor 分页模式下,响应中的 total 和 offset 为 null。需要恢复导出任务时,应保存最后成功处理页面的 next_cursor 以及完整筛选条件。
状态
| 状态 | 含义 |
|---|---|
SUCCESS | 调用成功并生成正常用量记录 |
PARTIAL | 已获得部分可用的用量信息 |
FAILED | 调用失败 |
UNKNOWN_MODEL | 当前记录无法匹配模型计费信息 |
REFUNDED | 该记录已退款 |
默认查询仅返回 SUCCESS 和 PARTIAL。查询失败或退款记录时,请传入对应的 status;查询全部状态时,请设置 charged_only=false 且不指定 status。
错误处理与频率限制
| HTTP 状态码 | 常见原因 | 处理方式 |
|---|---|---|
400 | 查询参数格式、时间范围或参数组合无效。 | 检查 limit、offset、cursor、status 和 ISO 8601 时间格式;使用 cursor 时移除非零 offset。 |
401 | API Key 缺失、无效或环境变量未加载。 | 检查服务端的 UNIGATEWAY_API_KEY,重新加载应用配置后再次请求。 |
403 | API Key 已停用或没有访问权限。 | 在 UniGateway 控制台检查该 API Key 的状态、访问控制和限额,再重新验证。 |
429 | 查询频率超过限制。 | 降低并发,使用指数退避重试,并从最后成功页面的 next_cursor 继续。 |
500 | 服务暂时无法处理请求。 | 使用有限次数的指数退避重试;持续失败时记录请求时间、筛选条件和错误响应以便排查。 |
每个 API Key 的用量查询频率上限为每分钟 120 次。轮询场景应按需延长间隔;批量导出优先使用 limit=100 至 200 和 cursor 分页,避免同时对同一 API Key 发起大量请求。
常见问题
Q: 为什么查询结果中没有其他 API Key 的记录?
GET /v1/usage 只返回用于本次鉴权的 API Key 自身产生的记录。请使用需要核对的 API Key 发起请求,并确认服务端环境变量已切换为该密钥;接口不会合并同一账户下其他 API Key 的数据。
Q: 为什么刚完成的调用没有出现在结果中?
先确认请求使用的是目标 API Key,再扩大 from 与 to 的时间范围,并检查时间是否使用 ISO 8601 格式和正确时区。默认仅返回 SUCCESS 与 PARTIAL;排查失败或退款记录时,传入对应的 status,或设置 charged_only=false 后重新查询。
Q: 金额字段为什么是字符串?
original_price、savings_amount、final_price 和相关缓存价格字段以十进制字符串返回,以避免浮点精度损失。应用侧应使用高精度十进制类型完成汇总和金额展示,不要直接将其转换为二进制浮点数。