调用日志 API
用同一把 sk-gpushare-* Key 拉取逐笔调用记录 —— 状态、报错原因、异步任务终态、实扣积分,可增量同步进自己的系统
logs.dflop.top 上那本逐笔账,现在可以用同一把 API Key 直接拉。典型用途:
- 把调用流水同步进自己的系统做对账 / 报表 / 告警
- 查某一笔为什么失败(错误码、脱敏后的错误原文、网关内部换过几次道)
- 查异步任务的终态(视频 / 音乐 / 语音 / 出图):提交时拿到的
id粘进ref就能反查
只读接口,不消耗余额。余额为 0 的 Key 照常可以读日志 —— 那正是最需要查原因的时候。
| 端点 | 用途 |
|---|---|
GET /v1/logs | 逐笔调用,筛选 + 翻页 |
GET /v1/logs/{id} | 单笔的技术视图(请求参数 / 上游回包 / 网关) |
GET /v1/logs/summary | 时段聚合(按天 / 按模型 / 四种终态计数) |
Base URL https://api.dflop.top,鉴权与聊天端点同一套(Authorization: Bearer sk-gpushare-… 或 x-api-key),见鉴权。
可见范围(先读这一段)#
默认只能看到这把 Key 自己打出去的调用。 这是有意的:一把分发出去的 Key 不该能列举同账号其他 Key 的流水。
# 默认:只有这把 Key 的记录
curl "https://api.dflop.top/v1/logs?limit=5" \
-H "Authorization: Bearer $PLATFORM_API_KEY"
要一次拉走账号下全部 Key 的流水,需要账号主人在控制台逐把 Key 显式打开:
model.dflop.top → API Keys → 点进某把 Key → 勾选「允许这把 Key 读取账号全部调用日志」
打开后:
curl "https://api.dflop.top/v1/logs?scope=account&limit=5" \
-H "Authorization: Bearer $PLATFORM_API_KEY"
# 账号级下还可以按某一把 Key 收窄
curl "https://api.dflop.top/v1/logs?scope=account&key_id=<uuid>" \
-H "Authorization: Bearer $PLATFORM_API_KEY"
没开就传 scope=account → 403 permission_denied。
| 情况 | scope=key(默认) | scope=account |
|---|---|---|
| 自己在控制台创建的 Key | ✅ | 需勾选开关 |
| 企业签发给员工的 Key | ✅ | ❌ 恒不可用 |
| 算力包 / 桌面端凭据 | ✅ | ❌ 恒不可用 |
智能体 Key(/v1/agent/* 专用) | ❌ 403 | ❌ 403 |
GET /v1/logs#
查询参数#
| 参数 | 默认 | 说明 |
|---|---|---|
scope | key | key = 只看这把 Key;account = 账号下全部(需开关) |
key_id | — | 仅 scope=account:收窄到某一把 Key |
period | 30d | today / 7d / 30d / this_month / all。给了 from+to 时忽略 |
from / to | — | 必须成对给。YYYY-MM-DD 或 RFC3339 时间戳,两种形态可混用 |
model | — | 精确 model id,逗号多选:model=gpt-5.5,glm-4.7 |
status | — | 逗号多选:success / error / interrupted / rejected |
unit_type | — | 逗号多选:image / video / music / audio / avatar / voice / search / transcript,外加 chat(= 按 token 计费的对话行) |
error_code | — | 精确错误码,逗号多选。码表见错误码 |
ref | — | 按任务 ID 或请求 ID 精确反查(两列都试,不用分辨手里那串是哪一种) |
order | desc | desc 最新在前;asc 最旧在前(增量拉取用) |
limit | 50 | 1–200 |
cursor | — | 上一页响应里的 next_cursor,原样回传 |
include | attempts | 逗号多选:attempts(换道明细)/ in_flight(未完成的异步任务)/ none |
to的右端语义随形态而变,这一点务必注意:
to=2026-01-03(日期形态)→ 包含 1 月 3 日一整天to=2026-01-03T12:00:00Z(时间戳形态)→ 不含该时刻本身(created_at < to)增量同步请用时间戳形态 —— 上一次的
to原样当下一次的from,不重不漏。
筛选参数为空串 = 不筛,不是「筛掉一切」。
?status=与不传status等价。 状态值写错(如?status=failed)会得到 400,不会静默返回空页。
响应#
{
"object": "list",
"currency": "points", // 所有金额字段的单位:积分
"scope": "key", // 本次实际生效的可见范围
"period_from": "2026-10-08T00:00:00Z",
"period_to": "2026-11-07T03:21:00Z",
"data": [
{
"object": "log",
"id": 90210,
"created_at": "2026-11-07T03:18:42Z", // 终态时刻(异步任务 = 结算时刻)
"submitted_at": "2026-11-07T03:17:05Z", // 请求进入网关的时刻
"key_id": "…", "key_name": "prod", "key_prefix": "sk-gpushare-a1b2",
"model": "doubao-seedance-2.0",
"status": "success", // success|error|interrupted|rejected
"error_code": null,
"error_message": null,
"input_tokens": 0, "output_tokens": 0, "cached_tokens": 0,
"unit_count": 5, "unit_type": "video", // 按件计费行;null = 按 token 计费
"cost": "182.40", // 实扣积分,JSON **字符串**
"latency_ms": 97210,
"request_id": "3f9c…", // = 响应头 x-gateway-trace
"task_id": "9b1e…", // 异步任务提交响应里的那个 id
"result_urls": ["https://…"],
"result_expires_at": "2026-11-08T03:18:42Z",
"attempts_count": 1, // null = 这一轮没查(见下)
"attempts": [ // include=attempts 时才有
{ "seq": 1, "created_at": "…", "latency_ms": 4100,
"error_code": "upstream_error", "error_message": "上游暂时不可用" }
]
}
],
"has_more": true,
"next_cursor": "d:1762..._90210_1759..._1762..."
}
金额是 JSON 字符串("182.40"),不是数字 —— 刻意如此,不让钱经过 IEEE-754。解析时按十进制处理。
一行 = 一次对外调用的终态#
- 网关在多条上游线路间自动换道产生的失败尝试不单独成行、不计费、不计入请求数,它们折叠在对应终态行的
attempts[]里(attempts_count是条数)。 ⚠️attempts_count是null表示「这一轮没查到」—— 不带include=attempts,或那次子查询自己失败了。不是 0。回 0 会把「我们没看」说成「确实没换过道」,而这个字段的用途恰恰是判断「这笔有没有抖过」。 - 四种状态:
success成功 /error失败 /interrupted中断(客户端断开或网关超时)/rejected已拒绝(提交那一刻被网关拒:参数错误、模型不可用、余额不足、内容拦截,费用恒为 0,但会入账,便于核对「发了但没成」)。 - 异步任务要跑到终态结算后才成为账目行。 还在生成中的任务用
include=in_flight单独拿(默认不返回 —— 它比列表本身贵得多)。⚠️ 它只在第一页给(没有cursor的那次请求):翻页时一份就够了。翻页响应里该字段整个缺席,与「要了但确实没有在飞任务」(空数组)在 wire 上分得开。
翻页#
# 第一页
curl "https://api.dflop.top/v1/logs?period=7d&limit=100" -H "Authorization: Bearer $KEY"
# 拿 next_cursor 继续,直到 has_more 为 false
curl "https://api.dflop.top/v1/logs?cursor=<next_cursor>" -H "Authorization: Bearer $KEY"
游标是不透明串,原样回传。它冻住了第一页解析出的时间窗 —— 所以翻页途中 period=7d 不会随时间向前滑走、把最老的一小片漏掉。
⚠️ 游标里记了方向:拿 order=desc 的游标去请求 order=asc 会得到 400,不会静默给你一段错位的数据。
增量同步配方#
把逐笔流水持续拉进自己的库,推荐这个形状:
# 水位线 = 上一次成功同步到的时刻(RFC3339)
WATERMARK="2026-11-07T03:00:00Z"
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
curl -sS "https://api.dflop.top/v1/logs?from=${WATERMARK}&to=${NOW}&order=asc&limit=200" \
-H "Authorization: Bearer $PLATFORM_API_KEY"
# 翻完全部页后,把 WATERMARK 推进到 NOW
两条必须注意的:
- 按
id去重。 行的写入顺序在高并发下可能与created_at有极小的错位(数据库序列的正常行为),所以这不是严格意义上的变更流。把水位线往回退 5 分钟重叠拉一段、按id去重,就能覆盖这种错位。id全局唯一且不复用。 - 异步任务按终态时刻(
created_at)入账,不是提交时刻。 一个跑了 20 分钟的视频任务,会出现在它结算那一刻的时间窗里。想按提交时刻对齐,用行上的submitted_at。
GET /v1/logs/{id}#
单笔的技术视图 —— 这一单到底是什么分辨率、什么时长、上游报回来什么。{id} 是列表行里的 id。
curl "https://api.dflop.top/v1/logs/90210" -H "Authorization: Bearer $KEY"
{
"id": 90210,
"kind": "video", // video|image|music|audio|null(对话/检索)
"request": [{ "k": "resolution", "v": "1080p" }, { "k": "duration", "v": "5" }],
"upstream": [{ "k": "framespersecond", "v": "24" }, { "k": "billable_tokens", "v": "…" }],
"gateway": [{ "k": "client_protocol", "v": "openai_chat" }, { "k": "request_bytes", "v": "…" }],
"request_note": null // 参数为空时的原因(如任务结算后清空了快照)
}
- 字段名刻意不翻译:它们就是 API 文档里的原名,方便逐条对照。
- 三组都可能是空数组 —— 对话轮没有任务行,老任务没有上游快照。空 ≠ 错误。
- 可见范围与列表一致:没开账号级开关的 Key 去取别的 Key 那一行,得到 404。
GET /v1/logs/summary#
时段聚合,用来对账。
curl "https://api.dflop.top/v1/logs/summary?period=this_month" -H "Authorization: Bearer $KEY"
{
"object": "logs.summary",
"currency": "points",
"scope": "key",
"period_from": "…", "period_to": "…",
"total": { "requests": 1840, "cost": "9123.55",
"input_tokens": 8812340, "output_tokens": 412990, "cached_tokens": 6610220 },
"status_counts": { "success": 1802, "error": 21, "interrupted": 4, "rejected": 13 },
"by_day": [{ "date": "2026-11-01T00:00:00Z", "requests": 61, "cost": "302.10", "input_tokens": 0, "output_tokens": 0, "cached_tokens": 0 }],
"by_model": [{ "model": "gpt-5.5", "requests": 1204, "cost": "5120.00", "total_tokens": 7712330 }]
}
与列表面的差异,三条:
- 不吃行级筛选。
model/status/unit_type/error_code/ref传了会返回 400 —— 它是区间内的全量聚合,静默忽略这些参数会让你按?model=X拿回整个账号的数字然后记在 X 头上。要按模型看,直接读响应里的by_model[];要行级筛选用/v1/logs。 - 时段上限 365 天,且不接受
period=all—— 不封顶会拖垮共享资源。要更长的历史请分段拉。 - 结果缓存 60 秒(按整分钟对齐的时间桶)。刚落账的那一笔最多晚一分钟出现在这里;要实时对账用
/v1/logs(逐笔面没有这层缓存)。
status_counts 不含内部换道尝试;成功率的分母通常取 success + error + interrupted(rejected 的请求根本没跑到上游)。
限流#
| 默认 | 每把 Key 每分钟 60 次(三个端点共用这一档) |
| 与推理配额的关系 | 完全分开。狂拉日志不会让你的业务请求撞 429 |
| 响应头 | x-ratelimit-limit / x-ratelimit-remaining / x-ratelimit-reset(秒) |
| 超限 | 429 logs_rate_limited + Retry-After |
对账场景本来就不需要高频:每分钟拉一次、每次 200 条,足够跟上任何量级。
错误#
| 状态 | code | 含义 |
|---|---|---|
| 400 | invalid_request | 参数不合法(状态值写错 / 半截区间 / 游标方向不匹配 / include 里有不认识的 token / 给 /summary 传了行级筛选) |
| 401 | invalid_api_key | Key 不存在、已停用、已过期,或账号非活跃 |
| 403 | permission_denied | 未开启账号级开关就用 scope=account;或用智能体 Key 调本接口 |
| 404 | log_not_found | 该 id 不存在,或不在这把 Key 的可见范围内 |
| 429 | logs_rate_limited | 超过每分钟上限,按 Retry-After 重试 |
错误体是 OpenAI 形状:{"error": {"message", "type", "code"}}。完整说明见错误码。
与网页版 / CSV 的字段对照#
logs.dflop.top 的 CSV 导出与本接口是同一份数据,只有两处命名不同:
| CSV / 网页 | 本接口 | 说明 |
|---|---|---|
cost_usd | cost | 两者都是积分。CSV 那个列名是全平台去美元化之前的历史遗留,已有客户脚本按名字取值所以不改;新接口不继承它 |
logs[] | data[] | 换成 OpenAI 的列表形状(object + data + has_more) |
其余字段逐字同名同义。CSV 里的 attempts_count 对应本接口的同名字段,result_urls 在 CSV 里以 | 分隔、在这里是数组。
脱敏说明#
error_message / error_code 在离开服务端前会统一脱敏:URL、密钥串、上游线路名称一律遮蔽。这不是截断错误信息 —— 分类(error_code)与可读原因都保留,只是不暴露我们的上游拓扑。排障时把 request_id 一起提供即可。