调用日志 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://qianyi.dflop.top,鉴权与聊天端点同一套(Authorization: Bearer sk-gpushare-… 或 x-api-key),见鉴权。


可见范围(先读这一段)#

默认只能看到这把 Key 自己打出去的调用。 这是有意的:一把分发出去的 Key 不该能列举同账号其他 Key 的流水。

# 默认:只有这把 Key 的记录
curl "https://qianyi.dflop.top/v1/logs?limit=5" \
  -H "Authorization: Bearer $PLATFORM_API_KEY"

要一次拉走账号下全部 Key 的流水,需要账号主人在控制台逐把 Key 显式打开:

qianduan.dflop.top → API Keys → 点进某把 Key → 勾选「允许这把 Key 读取账号全部调用日志」

打开后:

curl "https://qianyi.dflop.top/v1/logs?scope=account&limit=5" \
  -H "Authorization: Bearer $PLATFORM_API_KEY"

# 账号级下还可以按某一把 Key 收窄
curl "https://qianyi.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#

查询参数#

参数默认说明
scopekeykey = 只看这把 Key;account = 账号下全部(需开关)
key_id—仅 scope=account:收窄到某一把 Key
period30dtoday / 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 精确反查(两列都试,不用分辨手里那串是哪一种)
orderdescdesc 最新在前;asc 最旧在前(增量拉取用)
limit501–200
cursor—上一页响应里的 next_cursor,原样回传
includeattempts逗号多选: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://qianyi.dflop.top/v1/logs?period=7d&limit=100" -H "Authorization: Bearer $KEY"
# 拿 next_cursor 继续,直到 has_more 为 false
curl "https://qianyi.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://qianyi.dflop.top/v1/logs?from=${WATERMARK}&to=${NOW}&order=asc&limit=200" \
  -H "Authorization: Bearer $PLATFORM_API_KEY"
# 翻完全部页后,把 WATERMARK 推进到 NOW

两条必须注意的:

  1. 按 id 去重。 行的写入顺序在高并发下可能与 created_at 有极小的错位(数据库序列的正常行为),所以这不是严格意义上的变更流。把水位线往回退 5 分钟重叠拉一段、按 id 去重,就能覆盖这种错位。id 全局唯一且不复用。
  2. 异步任务按终态时刻(created_at)入账,不是提交时刻。 一个跑了 20 分钟的视频任务,会出现在它结算那一刻的时间窗里。想按提交时刻对齐,用行上的 submitted_at。

GET /v1/logs/{id}#

单笔的技术视图 —— 这一单到底是什么分辨率、什么时长、上游报回来什么。{id} 是列表行里的 id。

curl "https://qianyi.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://qianyi.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含义
400invalid_request参数不合法(状态值写错 / 半截区间 / 游标方向不匹配 / include 里有不认识的 token / 给 /summary 传了行级筛选)
401invalid_api_keyKey 不存在、已停用、已过期,或账号非活跃
403permission_denied未开启账号级开关就用 scope=account;或用智能体 Key 调本接口
404log_not_found该 id 不存在,或不在这把 Key 的可见范围内
429logs_rate_limited超过每分钟上限,按 Retry-After 重试

错误体是 OpenAI 形状:{"error": {"message", "type", "code"}}。完整说明见错误码。


与网页版 / CSV 的字段对照#

logs.dflop.top 的 CSV 导出与本接口是同一份数据,只有两处命名不同:

CSV / 网页本接口说明
cost_usdcost两者都是积分。CSV 那个列名是全平台去美元化之前的历史遗留,已有客户脚本按名字取值所以不改;新接口不继承它
logs[]data[]换成 OpenAI 的列表形状(object + data + has_more)

其余字段逐字同名同义。CSV 里的 attempts_count 对应本接口的同名字段,result_urls 在 CSV 里以 | 分隔、在这里是数组。


脱敏说明#

error_message / error_code 在离开服务端前会统一脱敏:URL、密钥串、上游线路名称一律遮蔽。这不是截断错误信息 —— 分类(error_code)与可读原因都保留,只是不暴露我们的上游拓扑。排障时把 request_id 一起提供即可。