21 KiB
智谱 GLM Coding Plan 用量查询对接文档
文档目的:为后续开发者(或 AI 助手)提供智谱 GLM Coding Plan 用量查询的完整对接方案,基于 cc-switch 源码逆向 + 官方文档查证,内容经过用户实测验证(用户在 cc-switch 里能看到自己的套餐用量)。
权威来源:以
farion1231/cc-switch源码实现为准。该实现用户已验证可用。本地参考源码:
D:\AI Code\ESP-32\espcc-switch\(已克隆,可直接查阅)
一、背景
ESP32-S3-RLCD-4.2 小屏需要显示智谱 GLM Coding Plan 的:
- 5 小时限额已用百分比 + 重置时间
- 周限额已用百分比 + 重置时间(新套餐才有)
- token 消耗统计
- 套餐层级
智谱官方没有公开的用量查询 API 文档,但通过逆向 cc-switch 找到了实际可用的工作端点。
二、关键结论(一句话总结)
| 问题 | 答案 |
|---|---|
| 用什么端点查? | GET /api/monitor/usage/quota/limit |
| 怎么鉴权? | 纯 API Key,放 Authorization 头,不加 Bearer 前缀 |
| 需要浏览器 Cookie 吗? | 不需要(跟之前社区工具 coding-plan-monitor 不一样) |
| ESP-32 能直接用吗? | 能,纯 HTTP GET,esp_http_client 可 1:1 复现 |
| 怎么区分 5h vs 周? | 看 limits[].unit 字段:3=5h, 6=周 |
| 怎么判断老/新套餐? | limits[] 里 TOKENS_LIMIT 条目数量(1=老无周, 2=新有周) |
| 能拿到绝对 token 数吗? | 不能,quota/limit 只给百分比。绝对 token 要调 model-usage 端点 |
三、核心 API
端点
国内:GET https://open.bigmodel.cn/api/monitor/usage/quota/limit
国际:GET https://api.z.ai/api/monitor/usage/quota/limit
域名选择:
- 用户的 API Key 配在
open.bigmodel.cn→ 用国内 - 用户的 API Key 配在
api.z.ai→ 用国际 - 两条路径(path)完全一致,只是域名不同
域名选择参考代码(来自 cc-switch coding_plan.rs:302-308):
fn zhipu_quota_base(base_url: &str) -> &str {
if base_url.contains("bigmodel.cn") {
"https://open.bigmodel.cn"
} else {
"https://api.z.ai"
}
}
请求
GET /api/monitor/usage/quota/limit HTTP/1.1
Host: open.bigmodel.cn
Authorization: <你的智谱 API_KEY>
Content-Type: application/json
Accept-Language: en-US,en
关键点:
- 方法:
GET - 鉴权:
Authorization头直接放 API Key 原值,不要加Bearer前缀- cc-switch 在
coding_plan.rs:319显式注释:// 注意:智谱不加 Bearer 前缀 - 这跟 OpenAI、Anthropic 等标准 Bearer 鉴权不同,是个例外
- cc-switch 在
- Content-Type:
application/json - Accept-Language:
en-US,en(cc-switch 加了这个,建议照抄) - 无请求体
- 无查询参数
- 建议超时:15 秒
- 不需要浏览器 Cookie
- 不需要伪装 User-Agent
返回 JSON 结构
{
"success": true,
"msg": "操作成功",
"data": {
"level": "Max",
"limits": [
{
"type": "TOKENS_LIMIT",
"unit": 3,
"number": 5,
"percentage": 32.5,
"nextResetTime": 1773734366338
},
{
"type": "TOKENS_LIMIT",
"unit": 6,
"number": 7,
"percentage": 45.0,
"nextResetTime": 1774663282997
}
]
}
}
字段说明
| 字段路径 | 类型 | 含义 |
|---|---|---|
success |
bool | 业务级成功标记,false 时读 msg |
msg |
string | 错误文案(success=false 时有用) |
data.level |
string | 套餐层级(如 "Max" / "Pro" / "Lite") |
data.limits[] |
array | 限额窗口数组 |
data.limits[].type |
string | "TOKENS_LIMIT"(我们要的) / "TIME_LIMIT"(使用时长,跳过) |
data.limits[].unit |
int | 窗口类型,核心字段:3 = 5 小时,6 = 每周 |
data.limits[].number |
int | 数量(5h 时为 5,周时为 7) |
data.limits[].percentage |
float | 已用百分比 0-100 |
data.limits[].nextResetTime |
int(ms) | 重置毫秒时间戳;0% 用量时可能缺失 |
四、字段→业务含义映射
| 屏幕显示 | 字段路径 | 备注 |
|---|---|---|
| 5 小时限额已用% | limits[type=TOKENS_LIMIT && unit=3].percentage |
0-100 |
| 5 小时重置时间 | limits[type=TOKENS_LIMIT && unit=3].nextResetTime |
ms 时间戳 |
| 周限额已用% | limits[type=TOKENS_LIMIT && unit=6].percentage |
新套餐才有,老套餐查不到 |
| 周限额重置时间 | limits[type=TOKENS_LIMIT && unit=6].nextResetTime |
同上 |
| 套餐层级 | data.level |
字符串 |
时间戳转换规则(用于显示)
根据之前 UI 设计讨论的结果,重置时间的显示策略:
- 距离当前时间 ≤ 24 小时 → 显示
HH:MM(如18:42) - 距离当前时间 > 24 小时 → 显示
MM-DD(如06-28)
参考实现(formatResetTime):
function formatResetTime(resetMs) {
const diff = resetMs - Date.now();
const absDiff = Math.abs(diff);
const date = new Date(resetMs);
const pad = n => String(n).padStart(2, '0');
if (absDiff <= 24 * 3600 * 1000) {
return `${pad(date.getHours())}:${pad(date.getMinutes())}`;
} else {
return `${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
}
}
ESP-32 C 实现可用 <time.h> 的 localtime_r + difftime。
五、⚠️ 三个必避的坑
坑 1:必须用 unit 字段区分 5h 和周,不能按重置时间排序
问题:cc-switch 在 issue #3036 踩过——每周周期末尾,周窗口会比 5 小时窗口更早重置,按 nextResetTime 升序排序必然把"5h"和"周"标反。
正确做法:只看 unit 字段:
unit == 3→ 5 小时窗口unit == 6→ 周窗口
只有当 unit 缺失或未识别时,才回落到启发式(无 reset 优先归 5h,其余按 reset 升序填空槽)。
源码引用:cc-switch/src-tauri/src/services/coding_plan.rs:226-234 有显式注释说明这个坑。
坑 2:老套餐 vs 新套餐的判定
判定方式不是某个标志位,而是 limits[] 数组里 type==TOKENS_LIMIT 条目的数量:
- 老套餐(2026-02-12 前订阅):只返回 1 条
TOKENS_LIMIT(只有 5h,无周) - 新套餐:返回 2 条
TOKENS_LIMIT(5h + 周)
当前用户:用的是老套餐(无周限额),所以查出来只会看到 1 条 TOKENS_LIMIT,没有 unit=6 的条目。
代码处理建议:用两个可空 slot(five_hour / weekly),有就填、没有就留空。屏幕显示时:
- 5h 没查到 → 显示 "—"
- 周限额没查到 → 显示 "无限制" 或 "—" 或 "OLD PLAN"
源码参考:cc-switch/src-tauri/src/services/coding_plan.rs:279-291 实现了天然降级逻辑。
坑 3:智谱接口只给百分比,不给绝对 token 数
问题:cc-switch 在 coding_plan.rs:282-289 明确:used_value_usd / max_value_usd 都是 None。智谱接口只返回 percentage,不返回绝对 token 数。
影响:之前 UI 设计里说的"token 消耗按 M/B 显示"不能从 quota/limit 接口拿到。
解决方案:要拿绝对 token 数,得调另一个端点:
GET /api/monitor/usage/model-usage?startTime=...&endTime=...
- 参数:
startTime/endTimeURL 编码后的"yyyy-MM-dd HH:mm:ss"格式 - 官方插件 zai-coding-plugins 的默认窗口:昨天当前小时整点 → 今天当前小时末秒
- cc-switch 没用这个接口,所以返回字段结构需要实测确认(官方插件只是
JSON.stringify原样打印,没固定字段名)
建议:
- 先做百分比显示(已用 X% + 重置时间),这是 quota/limit 能直接拿到的
- 绝对 token 数等 model-usage 端点字段确认后再加,或者干脆本地累加(每次调 LLM 时把
response.usage.total_tokens累计起来,自己统计今日/本周消耗)
六、ESP-32 实现参考(伪代码)
#include "esp_http_client.h"
#include "cJSON.h"
// 1. 构造请求
esp_http_client_config_t config = {
.url = "https://open.bigmodel.cn/api/monitor/usage/quota/limit",
.method = HTTP_METHOD_GET,
.timeout_ms = 15000,
.cert_pem = bigmodel_root_ca, // 需要 HTTPS 根证书
.user_agent = "esp32-rlcd", // 随便写,没限制
};
esp_http_client_handle_t client = esp_http_client_init(&config);
// 关键:Authorization 不加 Bearer 前缀!
char auth_header[256];
snprintf(auth_header, sizeof(auth_header), "%s", api_key);
esp_http_client_set_header(client, "Authorization", auth_header);
esp_http_client_set_header(client, "Content-Type", "application/json");
esp_http_client_set_header(client, "Accept-Language", "en-US,en");
// 2. 发请求
esp_err_t err = esp_http_client_perform(client);
if (err != ESP_OK) { /* 错误处理 */ }
int status = esp_http_client_get_status_code(client);
if (status == 401 || status == 403) {
// 凭据失效
} else if (status != 200) {
// 其他错误
}
// 3. 读响应体
char buffer[4096];
int len = esp_http_client_read(client, buffer, sizeof(buffer) - 1);
buffer[len] = '\0';
// 4. 解析 JSON
cJSON *root = cJSON_Parse(buffer);
cJSON *data = cJSON_GetObjectItem(root, "data");
cJSON *level = cJSON_GetObjectItem(data, "level");
const char *plan_level = level->valuestring; // "Max"
cJSON *limits = cJSON_GetObjectItem(data, "limits");
cJSON *limit;
cJSON_ArrayForEach(limit, limits) {
cJSON *type = cJSON_GetObjectItem(limit, "type");
if (!cJSON_IsString(type) || strcmp(type->valuestring, "TOKENS_LIMIT") != 0) {
continue; // 跳过 TIME_LIMIT
}
cJSON *unit = cJSON_GetObjectItem(limit, "unit");
cJSON *pct = cJSON_GetObjectItem(limit, "percentage");
cJSON *reset = cJSON_GetObjectItem(limit, "nextResetTime");
switch (unit->valueint) {
case 3: // 5 小时窗口
five_hour_pct = pct->valuedouble;
five_hour_reset_ms = reset ? reset->valuedouble : 0;
break;
case 6: // 周窗口(新套餐才有)
weekly_pct = pct->valuedouble;
weekly_reset_ms = reset ? reset->valuedouble : 0;
break;
}
}
cJSON_Delete(root);
esp_http_client_cleanup(client);
// 5. 转换时间戳为显示字符串(24h 内显示 HH:MM, 否则 MM-DD)
// 使用 <time.h> 的 localtime_r + difftime
七、鉴权验证(写代码前先做)
写代码前,先用 curl 验证 API Key 能不能直接调通。这是最省时间的步骤——避免代码写完才发现鉴权走不通。
curl -X GET \
-H "Authorization: <你的智谱 API_KEY>" \
-H "Content-Type: application/json" \
-H "Accept-Language: en-US,en" \
https://open.bigmodel.cn/api/monitor/usage/quota/limit
预期响应(老套餐):
{
"success": true,
"msg": "操作成功",
"data": {
"level": "Max",
"limits": [
{
"type": "TOKENS_LIMIT",
"unit": 3,
"number": 5,
"percentage": 32.5,
"nextResetTime": 1773734366338
}
]
}
}
判定:
- ✅ 返回 200 + 有
data.limits→ API Key 路线可行,ESP-32 可以直接复现 - ❌ 返回 401/403 → API Key 无效或权限不够
- ❌ 返回空 body 或非 JSON → 可能被反爬,但根据 cc-switch 经验这种情况不应该发生(cc-switch 用纯 API Key 跑通了)
八、源码引用清单
权威实现(cc-switch,用户已验证):
| 文件 | 行 | 作用 |
|---|---|---|
src-tauri/src/services/coding_plan.rs |
302-324 | query_zhipu 端点+请求头构造 |
src-tauri/src/services/coding_plan.rs |
319 | "// 注意:智谱不加 Bearer 前缀" |
src-tauri/src/services/coding_plan.rs |
218-224 | classify_zhipu_window 按 unit 分类 |
src-tauri/src/services/coding_plan.rs |
226-234 | unit 字段坑的注释(issue #3036) |
src-tauri/src/services/coding_plan.rs |
238-292 | parse_zhipu_token_tiers 解析逻辑 |
src-tauri/src/services/coding_plan.rs |
279-291 | 老/新套餐天然降级(可空 slot) |
src-tauri/src/services/coding_plan.rs |
282-289 | 只读 percentage,绝对值为 None |
src-tauri/src/services/subscription.rs |
301, 308 | TIER_FIVE_HOUR / TIER_WEEKLY_LIMIT 常量 |
src-tauri/src/services/coding_plan.rs |
1224-1240 | 测试:老套餐单 tier 降级到 5h |
src-tauri/src/services/coding_plan.rs |
1316-1333 | 测试:unit 字段覆盖 reset 排序 |
本地路径:D:\AI Code\ESP-32\espcc-switch\(已克隆)
辅助参考(官方插件):
zai-coding-plugins/plugins/glm-plan-usage/skills/usage-query-skill/scripts/query-usage.mjs— 调用了 3 个端点(quota/limit + model-usage + tool-usage),quota/limit 字段处理跟 cc-switch 一致
辅助参考(社区工具,鉴权方式不同,不要照抄):
JinHanAI/coding-plan-monitor/src/providers/zhipu.ts— 走浏览器 Cookie,不支持 API Key 直连
九、官方文档(背景参考)
- 智谱AI开放文档 - Coding Plan FAQ — 5h + 周限额机制说明
- 智谱AI开放文档 - 套餐概览 — 各套餐限额参考表
- 智谱AI开放文档 - 用量查询插件 — 官方插件介绍
- 智谱AI开放文档 - HTTP API 调用 — 基础 HTTP 调用规范
- cc-switch GitHub — 权威实现来源
- cc-switch Issue #1857 - 用量统计与计费追踪
- cc-switch Issue #1588 - 智谱用量查询脚本示例
十、对接 Checklist(给 codex 用的执行清单)
- Step 1:用 curl 验证 API Key 能调通(参考第七节)
- Step 2:在 ESP-32 项目里加 HTTPS 根证书(open.bigmodel.cn 的根 CA)
- Step 3:实现
zhipu_query_quota()函数,封装 HTTP GET + JSON 解析 - Step 4:实现
unit字段分类(3=5h, 6=周),不要按时间排序 - Step 5:实现老/新套餐降级(周限额查不到时显示"无限制")
- Step 6:实现时间戳→显示字符串转换(24h 内 HH:MM,否则 MM-DD)
- Step 7:绝对 token 数(如果需要)→ 调
model-usage端点实测字段,或本地累加每次 LLM 调用的usage.total_tokens - Step 8:把查询结果通过 BLE/USB 推送到板子(参考现有
metrics_*模块的推送方式)
附:接口对比速查表
| 项目 | 端点 | 鉴权 | 用户验证 | 推荐用 |
|---|---|---|---|---|
| cc-switch | /api/monitor/usage/quota/limit |
纯 API Key(无 Bearer) | ✅ 用户已验证 | ✅ 本文档基准 |
| zai-coding-plugins(官方) | 同上 + model-usage + tool-usage |
API Key(无 Bearer) | ✅ 官方插件 | 辅助参考 |
| coding-plan-monitor(社区) | 同上 | 浏览器 Cookie | ⚠️ 仅浏览器 | ❌ 不照抄 |
十一、token 使用量获取方案
核心结论:
quota/limit端点只返回百分比,不返回绝对 token 数。要拿"今日用了多少 token",有三种方案,各有取舍。
三种方案对比
| 方案 | 原理 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|---|
| A. 调 model-usage 端点 | 查询时间窗口内的 token 消耗明细 | 服务端权威数据,无需本地存储 | ⚠️ 字段结构官方未公开,需实测 | ⭐⭐⭐ |
| B. 本地累加(推荐) | 每次调 LLM 时累计响应里的 usage.totalTokens |
字段确定(智谱对话补全 API 标准),实现简单 | 只统计"经过本代理的"调用,跨设备/跨工具会漏 | ⭐⭐⭐⭐⭐ |
| C. 只显示百分比 | 直接用 quota/limit 的 percentage |
零开发量,已有数据 | 没有绝对值,信息量少 | ⭐⭐ |
方案 A:model-usage 端点(需实测字段)
端点:
GET https://open.bigmodel.cn/api/monitor/usage/model-usage?startTime=...&endTime=...
请求(跟 quota/limit 同款鉴权):
GET /api/monitor/usage/model-usage?startTime=2026-06-23%2000%3A00%3A00&endTime=2026-06-23%2023%3A59%3A59 HTTP/1.1
Host: open.bigmodel.cn
Authorization: <API_KEY> ← 不加 Bearer
Content-Type: application/json
Accept-Language: en-US,en
参数:
startTime/endTime:URL 编码后的"yyyy-MM-dd HH:mm:ss"格式- 官方插件默认窗口:昨天当前小时整点 → 今天当前小时末秒
返回 JSON 结构(⚠️ 推断,需实测):
{
"success": true,
"code": 200,
"msg": "ok",
"data": {
"usageList": [ // 字段名推断, 实测可能不同
{
"modelName": "glm-4.6",
"totalTokens": 123456,
"promptTokens": 100000, // 智谱用驼峰命名(不是 prompt_tokens)
"completionTokens": 23456
}
]
}
}
⚠️ 字段未公开的证据:
- 官方插件
query-usage.mjs:167调 model-usage 时没传 postProcessor,line 147 直接JSON.stringify(outputData)原样打印给 LLM - 官方 agent 文档
usage-query-agent.md也没描述字段结构 - 说明官方故意不固定字段文档(可能经常变),让 LLM 运行时自适应解读
实测步骤(必做):
curl -X GET \
-H "Authorization: <你的 API_KEY>" \
-H "Content-Type: application/json" \
-H "Accept-Language: en-US,en" \
"https://open.bigmodel.cn/api/monitor/usage/model-usage?startTime=$(date -d 'yesterday' +%Y-%m-%d)%2000%3A00%3A00&endTime=$(date +%Y-%m-%d)%2023%3A59%3A59"
拿到真实 JSON 后,根据实际字段名写解析代码。
方案 B:本地累加(推荐 ⭐)
原理:智谱对话补全 API 的每次响应都带 usage 字段,本地累加即可。
智谱对话补全响应里的 usage 字段(官方文档确认):
{
"choices": [ ... ],
"usage": {
"promptTokens": 123, // 输入 token(驼峰命名!)
"completionTokens": 456, // 输出 token
"totalTokens": 579 // 总计
}
}
⚠️ 命名坑:智谱用驼峰(promptTokens),不是 OpenAI 的下划线(prompt_tokens)。解析时别搞混。
实现方式(cc-switch 就是这么做的):
- cc-switch 在
src-tauri/src/proxy/usage/parser.rs:17-22定义了TokenUsage结构 - 每次代理 LLM 请求时,从响应里解析
usage,写入本地 SQLite(usage_stats.rs) - 查询时
get_usage_summary聚合统计
ESP-32 上的简化实现:
- 如果电脑端有代理服务(像 cc-switch 那样),在代理层累加 token,定期推给板子
- 如果板子自己调 LLM,在板子上累加(但 ESP-32 存储有限,建议只存今日/本周两个计数器)
为什么推荐:
- 字段确定(智谱对话补全 API 标准,有官方文档)
- 实现简单(就是累加)
- 不依赖未公开的 model-usage 端点
- cc-switch 验证过这条路可行
方案 C:只显示百分比
直接用 quota/limit 的 TOKENS_LIMIT.percentage,显示"5H 已用 32%"。
适合 MVP 阶段——零开发量,数据现成。后续再加方案 A 或 B 补绝对值。
给 codex 的建议
- 第一阶段:用方案 C(百分比),先把 quota/limit 跑通
- 第二阶段:如果电脑端有代理服务,用方案 B(本地累加)——字段确定、实现简单
- 第三阶段:如果需要服务端权威数据,用方案 A——但必须先 curl 实测字段,不要按本文档的推断结构写代码
十二、cc-switch 的 token 统计实现(方案 B 参考)
cc-switch 不调 model-usage 端点,而是本地 SQLite 累加。这是方案 B 的最佳参考。
核心文件
| 文件 | 作用 |
|---|---|
src-tauri/src/proxy/usage/parser.rs |
从 LLM 响应解析 TokenUsage(支持 Claude/OpenAI 格式) |
src-tauri/src/services/usage_stats.rs |
SQLite 存储 + 聚合查询(get_usage_summary) |
src-tauri/src/services/session_usage_codex.rs |
Codex 专用 session 用量 |
TokenUsage 结构(parser.rs:17-22)
pub struct TokenUsage {
pub input_tokens: u32,
pub output_tokens: u32,
pub cache_read_tokens: u32,
pub cache_creation_tokens: u32,
}
解析入口
from_claude_response(parser.rs:66)— 解析 Claude API 响应的usage.input_tokens/output_tokens- 也支持 OpenAI 兼容格式(
prompt_tokens/completion_tokens)
统计查询
get_usage_summary(usage_stats.rs:505-664)支持按时间范围聚合,返回各模型的 token 总量。
本地路径:D:\AI Code\ESP-32\espcc-switch\src-tauri\src\proxy\usage\parser.rs
Sources(token 使用量部分)
- 智谱AI开放文档 - 核心参数(Token 定义) — promptTokens/completionTokens/totalTokens 定义
- 智谱AI开放文档 - 对话补全 API — usage 字段在响应里的位置
- 智谱AI开放文档 - API 价格 — token 计费说明
- GitHub Issue zhayujie/CowAgent#2289 — 智谱驼峰命名 vs OpenAI 下划线命名的兼容性问题
- cc-switch Discussion #1038 - 智谱用量查询脚本 — model-usage 端点调用示例
- 本地源码:
D:\esp\zai-coding-plugins\plugins\glm-plan-usage\skills\usage-query-skill\scripts\query-usage.mjs(官方插件,证实 model-usage 字段未公开) - 本地源码:
D:\AI Code\ESP-32\espcc-switch\src-tauri\src\proxy\usage\parser.rs(本地累加实现)