逐单计费:请求 ID 与实扣查询
每一次调用都有一个请求 ID,和一个最终实扣金额。LLM 请求把 ID 放在响应头 x-apimodels-request-id 里,金额直接写在响应体的 usage.cost 里(流式在最后带用量的那一帧),非流式请求的响应头还带 x-apimodels-cost;图片、视频、音频任务的 ID 就是创建时返回的 task_id,创建时还会告诉你预扣了多少。拿任何一个 ID 调 GET /v1/records/{id},拿到的就是这笔请求从余额里扣掉的准确金额 —— 经书面许可转售我们的服务(服务条款第 6 节,联系 business@apimodels.app)、或者要给自己的用户逐笔对账时用它。
一览
- Endpoint
- GET /api/v1/records/{task_id}
- 鉴权
- Authorization: Bearer <API_KEY>
- 计费
- 免费(查询不扣积分)
- 响应头
- x-apimodels-request-id · x-apimodels-cost
- 响应体
- usage.cost · reservedCredits
- 覆盖
- LLM、图片、视频、音频的每一次调用
- 货币单位
- USD
请求 ID 从哪来
LLM 端点(/v1/chat/completions、/v1/messages、Gemini 原生 /v1beta、/v1/responses、/v1/completions)的每个成功响应都带 x-apimodels-request-id 响应头,值就是这笔请求的 task_id。非流式响应另带 x-apimodels-cost:单位 USD、已含你账户的全部折扣,是扣费完成后才写进去的实扣额。流式响应只带 ID —— 金额要等最后一个 chunk 结算后才有,拿 ID 去查记录即可。
HTTP/2 200
content-type: application/json
x-apimodels-request-id: cmumilhfd0013mg012q3qobr9
x-apimodels-cost: 0.0001
access-control-expose-headers: x-apimodels-request-id, x-apimodels-cost响应体里的 usage.cost
用 OpenAI / Anthropic SDK 的话通常读不到响应头,所以同一笔金额也写进了响应体的 usage.cost。非流式响应里 usage.cost.charged 就是实扣额,和 x-apimodels-cost 是同一个数。
{
"id": "chatcmpl-…",
"choices": [ … ],
"usage": {
"prompt_tokens": 165,
"completion_tokens": 6,
"cost": { "currency": "USD", "amount": 0.0001, "discount": 1, "charged": 0.0001 }
}
}流式响应在最后那一帧带用量的数据里给出 usage.cost:OpenAI 格式是 stream_options.include_usage 打开后的用量帧,Anthropic 格式(/v1/messages)是 message_delta,/v1/responses 是 response.completed 里的 response.usage。这一帧发出时扣费还没落库,所以 charged 是用同一个公式算出的应扣额;只有余额不足、没扣满的请求,实扣会比它少,以记录查询为准。message_start 里只有输入用量,不带 cost。
# OpenAI 格式(stream_options.include_usage = true):最后的用量帧
data: {"object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":165,"completion_tokens":6,"cost":{"currency":"USD","amount":0.0001,"discount":1,"charged":0.0001}}}
data: [DONE]
# Anthropic 格式(/v1/messages):message_delta
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"input_tokens":165,"output_tokens":6,"cost":{"currency":"USD","amount":0.0001,"discount":1,"charged":0.0001}}}| 字段 | 必填 | 类型 | 说明 |
|---|---|---|---|
| usage.cost.currency | — | string | 固定为 "USD" |
| usage.cost.amount | — | number | 按标价算的这一单金额,折扣前 |
| usage.cost.discount | — | number | 你账户的折扣倍数:1 = 不打折,0.95 = 95 折 |
| usage.cost.charged | — | number | 扣款,已含折扣。非流式 = 实扣额;流式 = 应扣额(见上) |
usage.cost 覆盖 /v1/chat/completions、/v1/messages、/v1/responses 和 /v1/completions;Gemini 原生 /v1beta 的响应不带它,金额照常用记录查询。
一个例外:非流式 LLM 请求跑超过约 95 秒时,网关会先发响应头保活,这时两个头已经发出去、带不上值,于是同样的两个值补进响应正文的 apimodels 字段。OpenAI / Anthropic 的 SDK 会忽略这个未知字段,不影响解析。
{
"id": "chatcmpl-…",
"choices": [ … ],
"usage": { … },
"apimodels": { "request_id": "cmumilhfd0013mg012q3qobr9", "cost": 0.0001 }
}图片、视频、音频任务的 ID 就是创建时返回的 data.taskId。创建响应里同时带 reservedCredits:建单时从余额里冻结的金额,是这单的上限;完成后按实际结算,多冻的部分自动退回,失败全额退回。
{
"code": 200,
"msg": "success",
"data": {
"taskId": "cmum4dq3z00dwlm01wugboo5z",
"state": "pending",
"model": "seedance-2.0",
"reservedCredits": 0.46,
"currency": "USD"
}
}任务完成后,GET ?task_id= 的查询结果和回调(callback_url)的 data 里都带 credits(最终实扣)和 currency,和记录查询的值一致。
{
"code": 200,
"msg": "success",
"data": {
"taskId": "cmum4dq3z00dwlm01wugboo5z",
"state": "completed",
"credits": 0.338,
"currency": "USD",
"resultUrls": ["https://r2.apimodels.app/videos/cmum4dq3z00dwlm01wugboo5z.mp4"]
}
}浏览器里直接调用时,这两个响应头已通过 Access-Control-Expose-Headers 暴露,fetch 的 response.headers.get() 能读到。
查询记录
GET /api/v1/records/{task_id}
Authorization: Bearer <API_KEY>示例:调用后逐单对账
cURL
curl https://api.apimodels.app/v1/records/cmumilhfd0013mg012q3qobr9 \
-H "Authorization: Bearer $API_KEY"Python
import requests, os
# 1) make the call and keep the request id from the response headers
r = requests.post(
"https://api.apimodels.app/v1/chat/completions",
headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
json={"model": "claude-haiku-5-5", "messages": [{"role": "user", "content": "Hi"}]},
)
request_id = r.headers["x-apimodels-request-id"]
print("charged now:", r.headers.get("x-apimodels-cost")) # non-streaming only
# 2) any time later: the authoritative billed amount for that one request
rec = requests.get(
f"https://api.apimodels.app/v1/records/{request_id}",
headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
).json()["data"]
print(rec["settled"], rec["credits"], rec["currency"])Node.js
const headers = { Authorization: "Bearer " + process.env.API_KEY };
// 1) make the call and keep the request id from the response headers
const r = await fetch("https://api.apimodels.app/v1/chat/completions", {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ model: "claude-haiku-5-5", messages: [{ role: "user", content: "Hi" }] }),
});
const requestId = r.headers.get("x-apimodels-request-id");
console.log("charged now:", r.headers.get("x-apimodels-cost")); // non-streaming only
// 2) any time later: the authoritative billed amount for that one request
const rec = await fetch("https://api.apimodels.app/v1/records/" + requestId, { headers });
const { data } = await rec.json();
console.log(data.settled, data.credits, data.currency);响应
{
"code": 200,
"msg": "success",
"data": {
"task_id": "cmumilhfd0013mg012q3qobr9",
"model": "claude-haiku-5-5",
"type": "LANGUAGE_MODEL",
"state": "completed",
"settled": true,
"credits": 0.0001,
"currency": "USD",
"usage": {
"input_tokens": 165,
"output_tokens": 6,
"cached_input_tokens": 149,
"cache_creation_tokens": 0
},
"created_at": 1790676571513,
"completed_at": 1790676572799
}
}| 字段 | 必填 | 类型 | 说明 |
|---|---|---|---|
| data.task_id | — | string | 请求 ID,与响应头 / 创建时返回的 taskId 相同 |
| data.model | — | string | 公开模型名,如 claude-haiku-5-5 |
| data.type | — | string | LANGUAGE_MODEL / TEXT_TO_IMAGE / IMAGE_TO_VIDEO / … 与调用记录一致 |
| data.state | — | string | pending(还在跑)/ completed / failed |
| data.settled | — | boolean | true 表示金额已是最终值;false 时 credits 为 null,稍后再查 |
| data.credits | — | number | null | 从余额里实际扣掉的美元,已含账户折扣与按模型定价;失败的请求为 0 |
| data.currency | — | string | 固定为 "USD" |
| data.usage | — | object | 仅 LLM:input_tokens、output_tokens、cached_input_tokens、cache_creation_tokens;推理 token 计入 output_tokens |
| data.created_at | — | number | 创建时间,毫秒时间戳 |
| data.completed_at | — | number | null | 完成时间,毫秒时间戳;未完成为 null |
金额的口径
- credits 是最终从余额里扣掉的数,不是标价乘用量:账户折扣、按模型协商价都已经算进去。
- 失败的请求一律 0。上游报错、被我们的校验拒绝、超时,都不收费。
- 流式 LLM 在最后一个 chunk 结算;按成片时长计费的视频模型在成片出来后结算。结算前 settled 为 false。
- x-apimodels-cost、非流式响应的 usage.cost.charged 与记录里的 credits 是同一个数;记录是权威来源,头和响应体只是省你一次查询。
- usage.cost.charged 约等于 amount × discount。LLM 的 amount 和 charged 都按 4 位小数取整、单笔最低 $0.0001;用了按模型协商价的账户,以 charged 为准。
- reservedCredits 是上限,不是账单:冻结期间这部分余额不能用于别的请求,结算后多的部分立刻退回可用余额。
- 金额保留到小数点后 6 位(百万分之一美元)。
错误
- 401 — API Key 缺失或无效
- 404 — 记录不存在。别的账户的请求 ID 也返回 404,不会确认它是否存在
- 500 — 服务器内部错误
常见问题
怎么知道某一次调用扣了多少钱?
从响应头读 x-apimodels-request-id(LLM),或记下创建时返回的 task_id(图片、视频、音频),然后带 API key 调 GET https://api.apimodels.app/v1/records/{id}。data.credits 就是折扣后实际从余额扣掉的美元数,失败的请求为 0。非流式 LLM 响应还直接在 x-apimodels-cost 头里给出金额。
为什么 credits 是 null、settled 是 false?
这笔请求还没结算完。流式 LLM 在最后一个 chunk 结算,部分视频模型按实际成片时长结算。完成后再查一次;settled 变成 true 后金额就是最终值,不会再变。
流式响应带金额头吗?
不带。流式只带 x-apimodels-request-id,因为金额要到流结束才知道,最后一个 chunk 之后去查 GET /v1/records/{id}。非流式两个头都带;跑超过约 95 秒的非流式 LLM 请求会改为在响应正文的 apimodels 字段里给出同样两个值,因为那时响应头已经为保活先发出去了。
能查别的账户的请求吗?
不能。属于别的账户的请求 ID 返回 404,和不存在的 ID 一样;接口不会确认这样的 ID 是否存在。查询本身免费,不计为调用。