运维与计费/用量详情查询 API

按推理请求 ID 查询单次请求的用量、实际费用和计费状态。

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_idPathstring是推理响应头 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}"
}

主要返回字段

字段类型说明
idstring用量记录 ID,与请求 ID 不同。
request_idstring本次查询使用的请求 ID。
token_id / token_namestring / string 或 null当前 API Key 的 ID 和名称。
model_id / model_namestring 或 null / stringmodel ID 和本次调用记录中的 model 名称。
prompt_tokens / completion_tokensnumber输入和输出 token 用量。
cache_read_tokens / cache_write_tokensnumber缓存读取和写入 token 用量。
total_tokensnumber记录中的总 token 数。不要自行将缓存 token 再次相加推导。
request_countnumber计费请求数。
is_streamboolean是否为流式请求。
billing_modestringPER_TOKEN、PER_REQUEST 或 PER_SECOND;以当前记录为准。
original_pricestring折扣前金额。
discount_percentage / discount_sourcestring 或 null折扣百分比和折扣来源;例如 "20" 表示优惠 20%。
savings_amountstring节省金额。
final_pricestring实际计费金额。
settlement_currencystring金额结算币种,当前固定为 USD。
currency / exchange_ratestring / stringmodel 原始定价币种,以及换算至结算币种时使用的汇率。
statusstringSUCCESS、PARTIAL、FAILED、UNKNOWN_MODEL 或 REFUNDED。
error_reasonstring 或 null异常原因标识。
error_reason_message / error_reason_actionstring 或 null异常说明及处理提示。
created_atstringISO 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.modalitystring媒体类型;图像请求为 image。
media_usage.resolutionstring 或 null记录中的输出分辨率。
media_usage.sizestring 或 null记录中的输出尺寸。
media_usage.qualitystring 或 null记录中的图像质量参数,示例为 auto。
media_usage.imageCountnumber 或 null图像数量。
media_usage.imageOutputUnitsnumber 或 null图像输出用量,示例为 201;不是图像数量。
media_usage.requestPathstring本次图像请求的接口路径,示例为 /v1/images/generations。

billing_detail 返回该记录的金额和折扣明细:

字段类型说明
billing_detail.originalPricestring折扣前金额,与顶层 original_price 表示相同金额,小数位数可能不同。
billing_detail.finalPricestring实际计费金额,与顶层 final_price 表示相同金额,小数位数可能不同。
billing_detail.discountApplied.sourcestring折扣来源;示例中的 NONE 表示未应用折扣。
billing_detail.discountApplied.percentagestring已应用的折扣百分比;示例中的 "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,也不要自行选择某条记录作为计费结果。