应用级 API
CZL Connect 提供一组面向接入方后端的"应用级 API",允许应用所有者用专属凭证调用 CZL Connect 管理本应用的用户。与 OAuth2 / OIDC 用户令牌不同,本 API 不依赖任何终端用户登录态,直接基于 Authorization: Bearer <api-key> 鉴权。
适用场景
- 接入方运营后台展示本应用用户列表、搜索、分页
- 接入方需要程序化调整某个用户的
groups分组 - 接入方需要踢人下线 (撤销其对本应用的全部 OAuth2 token)
- 接入方拉取本应用授权事件流做增量数据同步
- 接入方监控本应用授权次数 / 活跃用户 / MAU 等运营指标
与 OAuth2 用户令牌的区别
| 维度 | OAuth2 access token | 应用级 API key |
|---|---|---|
| 代表的身份 | 某个具体用户 | 整个应用 (作为客户端) |
| 来源 | 用户在浏览器走授权码流程 | 在应用后台手动创建 / 轮换 |
| 默认权限 | 拿到的用户可见数据 | 应用名下所有用户的管理操作 |
| 跨用户 | 不能,仅限自己的 userinfo | 可以,只要在 scope 范围内 |
| 跨应用 | 不能 | 不能 (一 key 绑一 App) |
| 适用调用方 | 用户的浏览器 / App | 接入方的服务端进程 |
不要把应用级 API key 下放给浏览器或终端 App 使用,它本质是"上帝模式"的密钥,任何持有者都能管理整个应用的用户。
凭证管理
进入应用详情页 → "API 接入" Tab,可以完成以下操作:
| 操作 | 描述 | 需要确认 |
|---|---|---|
| 新建凭证 | 选择 scope 后生成,明文只在创建瞬间返回一次,务必立即复制保存 | 通行密钥 |
| 轮换凭证 | 生成新 key,旧 key 进入 24 小时宽限期,期满自动停用 | 通行密钥 |
| 立即吊销 | 跳过宽限期,凭证立刻停用 | 通行密钥 |
| 删除凭证 | 从列表移除,不可恢复 | 通行密钥 |
凭证明文格式: czlc_live_<32 字符 base62 后缀>,数据库只存 SHA-256 hash 与前 12 个字符的明文前缀,后端无法反查完整凭证。
推荐的轮换流程
- 在前端点击"轮换",拿到新明文
- 把新明文部署到生产环境的配置 (环境变量 / 密钥管理)
- 等待生产环境实际开始使用新 key (可在凭证列表看到 LastUsedAt 更新)
- 24 小时内若一切正常,旧 key 自动过期,无需手动操作
- 若希望提前清退旧 key,可对旧 key 点击"立即吊销"
鉴权
请求头:
Authorization: Bearer czlc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
凭证验证失败返回 401,响应体:
{
"error": "invalid_api_key",
"message": "无效或已失效的 API 凭证"
}
凭证有效但缺少所需 scope 返回 403:
{
"error": "insufficient_scope",
"message": "凭证缺少所需 scope",
"required_any_of": ["user:read"],
"granted_scopes": ["stats:read"]
}
Scope 列表
| Scope | 涵盖端点 |
|---|---|
user:read | GET /users, GET /users/:userId, GET /events |
user:write | PATCH /users/:userId (修改 enabled) |
group:write | PATCH /users/:userId (修改 group) |
stats:read | GET /stats |
relation:revoke | POST /users/:userId/revoke |
GET /me 与 GET /groups 不需要任何 scope,只要凭证本身有效即可调用。
PATCH /users/:userId 支持同时传 group 与 enabled,此时分别要求 group:write 与 user:write,缺一即被 403 拒绝。
限流
- 限流单位: 单个 App,不区分凭证 (同一 App 下所有 key 共享一个桶)
- 默认配额: 600 次/分钟
- 响应头:
X-RateLimit-Limit/X-RateLimit-Remaining/X-RateLimit-Reset/Retry-After - 超限返回
429:
{
"error": "rate_limited",
"message": "请求过于频繁",
"retry_after": 23
}
API 端点
所有端点前缀: https://connect.czl.net/api/v1/app
URL 中不出现 appID,因为凭证已绑定唯一 App,服务端从凭证反查;这也避免接入方误填别人的 App ID。
GET /me
返回当前 key 绑定的 App 元信息 + 凭证 scopes。常用于接入方启动时做"凭证自检"。
请求示例:
curl -H "Authorization: Bearer czlc_live_xxx" \
https://connect.czl.net/api/v1/app/me
响应:
{
"app": {
"id": 42,
"name": "我的应用",
"description": "...",
"homeUrl": "https://app.example.com",
"logo": "https://...",
"enabled": true,
"isPublic": false,
"createdAt": "2026-05-01T08:00:00+08:00"
},
"apiKey": {
"id": 7,
"name": "prod backend",
"keyPrefix": "czlc_live_a3",
"scopes": ["user:read", "user:write", "group:write"],
"status": "active",
"lastUsedAt": "2026-05-18T10:23:45+08:00",
"createdAt": "2026-04-20T11:30:00+08:00"
}
}
GET /users
列出本 App 用户,支持分页和过滤。
Query 参数:
| 名称 | 类型 | 默认 | 说明 |
|---|---|---|---|
page | int | 1 | 页码,从 1 开始 |
pageSize | int | 20 | 每页条数,上限 100 |
keyword | string | - | 模糊匹配 username / email |
group | string | - | 精确匹配 group,例如 admin / t3 |
enabled | true/false | - | 按启停状态过滤,不传表示不过滤 |
响应:
{
"items": [
{
"userId": "1001",
"username": "alice",
"email": "alice@example.com",
"nickname": "",
"avatar": "https://...",
"emailVerified": true,
"group": "t2",
"enabled": true,
"authCount": 42,
"firstAuthAt": "2026-01-15T09:00:00+08:00",
"lastAuthAt": "2026-05-17T22:01:00+08:00",
"lastLoginAt": "2026-05-17T22:01:00+08:00",
"createdAt": "2026-01-15T09:00:00+08:00"
}
],
"total": 1234,
"page": 1,
"pageSize": 20
}
GET /users/:userId
获取单个用户在本 App 中的详情,字段同 /users 单条。userId 是用户在 CZL Connect 内部的数字 ID,可从 /users 列表或 OAuth2 userinfo 中获取。
用户不存在或未授权本 App 时返回 404:
{
"error": "user_not_found",
"message": "该用户不存在或未授权此应用"
}
PATCH /users/:userId
更新用户在本 App 中的 group 或 enabled 状态。
请求体 (两个字段都可选,至少提供一个):
{
"group": "t3",
"enabled": true
}
group 合法值: admin / viewer / t0 ~ t5,详见 GET /groups。
enabled 为 false 时,会同时撤销该用户在本 App 的所有 OAuth2 token (强制下线)。响应返回更新后的用户视图。
POST /users/:userId/revoke
撤销该用户对本 App 的全部 OAuth2 token,即"踢下线"。不会删除用户在本 App 的关系记录;用户下次访问本 App 时仍需走授权流程重新登录。
响应:
{
"revoked": true,
"revokedTokens": 3
}
GET /stats
本 App 的聚合统计。响应:
{
"totalUsers": 1234,
"totalAuths": 56789,
"activeUsers30d": 432,
"newUsers30d": 87,
"dailyAuthCounts": [
{ "date": "2026-05-17", "count": 234 },
{ "date": "2026-05-16", "count": 189 }
]
}
GET /events
本 App 的授权事件流,按时间倒序。适合接入方做增量数据同步: 持续轮询并记录最后一次拉到的 id 或 createdAt。
Query 参数:
| 名称 | 类型 | 默认 | 说明 |
|---|---|---|---|
limit | int | 50 | 单次最大返回数,上限 200 |
before | RFC3339 | - | 仅返回早于该时间的事件 |
eventType | string | - | 按事件类型过滤,例如 authorization_granted |
响应:
{
"items": [
{
"id": 99837,
"eventType": "authorization_granted",
"protocol": "oauth2",
"status": "success",
"userId": "1001",
"scope": "openid profile email",
"description": "",
"createdAt": "2026-05-17T22:01:00+08:00"
}
],
"limit": 50
}
事件类型常见取值: authorization_granted / authorization_revoked / code_issued / token_issued / token_refreshed / token_revoked。
GET /groups
返回所有可用 group + 层级展开说明。可用于接入方渲染下拉。
响应:
{
"items": [
{
"code": "admin",
"description": "管理员,可在本应用内管理用户、查看统计",
"impliedGroups": ["t0", "t1", "t2", "t3", "t4", "t5", "viewer", "admin"]
},
{
"code": "t3",
"description": "等级 3",
"impliedGroups": ["t0", "t1", "t2", "t3"]
}
],
"note": "t0-t5 按数字递增继承低等级 group;admin 独立,拥有全部权限;viewer 独立,仅可查看"
}
错误响应统一格式
所有 4xx / 5xx 响应都遵循:
{
"error": "<machine_readable_code>",
"message": "<human readable text>"
}
常见 error 取值:
| code | HTTP | 含义 |
|---|---|---|
missing_api_key | 401 | 缺少 Authorization 头 |
invalid_api_key | 401 | 凭证格式错误 / 已失效 / 不存在 |
insufficient_scope | 403 | 凭证有效但缺少所需 scope |
rate_limited | 429 | 触发限流 |
user_not_found | 404 | 用户不存在或未授权本 App |
invalid_body | 400 | 请求体不合法 |
empty_body | 400 | PATCH 时没提供任何可更新字段 |
invalid_user_id | 400 | :userId 参数无效 |
query_failed | 500 | 内部查询异常 |
context_missing | 500 | 内部上下文缺失 (通常意味着部署问题) |
调用示例
cURL
# 拉用户列表
curl -H "Authorization: Bearer czlc_live_xxx" \
"https://connect.czl.net/api/v1/app/users?page=1&pageSize=20"
# 修改 group
curl -X PATCH -H "Authorization: Bearer czlc_live_xxx" \
-H "Content-Type: application/json" \
-d '{"group":"t3"}' \
https://connect.czl.net/api/v1/app/users/1001
# 踢人下线
curl -X POST -H "Authorization: Bearer czlc_live_xxx" \
https://connect.czl.net/api/v1/app/users/1001/revoke
Go
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
const apiKey = "czlc_live_xxx"
func listUsers() {
req, _ := http.NewRequest("GET", "https://connect.czl.net/api/v1/app/users?pageSize=50", nil)
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
_ = json.RawMessage{}
}
Node.js
const API_KEY = process.env.CZL_CONNECT_API_KEY!;
async function setGroup(userId: string, group: string) {
const r = await fetch(`https://connect.czl.net/api/v1/app/users/${userId}`, {
method: "PATCH",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ group }),
});
if (!r.ok) throw new Error(`${r.status} ${await r.text()}`);
return r.json();
}
安全建议
- 凭证只下发到服务端,不要放进前端代码 / 移动 App / 公开仓库
- 不同环境 (生产 / 预发 / 沙箱) 使用不同凭证,便于事故时精准吊销
- 启用 scope 最小化: 只给必要的 scope,不要默认全选
- 凭证泄漏怀疑时立刻"立即吊销" + 新建,不走宽限期
- 定期 (例如季度) 主动轮换一次,降低长期暴露风险
- 监控接入方后端日志,关注异常的
429/403响应