API Keys API
创建和管理用于调用 Agent、工作流与上传接口的用户级 API Key
API Key 用于在没有用户会话的场景(服务端集成、脚本、iframe 嵌入)调用 Clouisle。Key 属于创建它的用户,不是团队资源:它的可访问范围由该用户的角色、团队成员关系和 Key 自身的绑定共同决定。基础路径为 /api/v1/api-keys。
密钥形态与生效条件
API Key 由服务端生成,形如:
clou_a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890
└──┬─┘└───────────────────────────┬───────────────────────────┘
clou_ 前缀 32 字节随机数的十六进制表示(64 位)| 项目 | 值 |
|---|---|
| 完整长度 | 69 个字符(clou_ + 64 位十六进制) |
| 展示前缀 | 前 12 个字符(clou_ + 8 位),用于识别与检索 |
| 存储方式 | 仅存储哈希与前缀,完整密钥只在创建响应中出现一次 |
| 创建者 | 创建时绑定到当前用户(user_id),不可转移 |
请求时放进 Authorization 头,与 JWT 使用同一种 Bearer 形式:
Authorization: Bearer clou_a1b2c3d4e5f6...一次 API Key 认证实际会校验四件事,任何一项不满足都会被拒绝:
- 激活状态:
is_active = false的 Key 不再参与匹配,返回 HTTP401/2001(INVALID_TOKEN)。 - 过期时间:
expires_at非空且已过期时返回 HTTP401/2002(TOKEN_EXPIRED);留空表示永不过期。 - 所有者账号状态:Key 所属用户被停用或待审批时返回 HTTP
401/2004(INACTIVE_USER)。 - 资源绑定:当 Key 绑定了 Agent 或 Workflow 列表时,只能访问列表内的资源;访问列表外资源返回 HTTP
403/3000。
scopes 与 rate_limit 会被保存并在响应中回显,但当前版本不做任何强制校验:不存在作用域中间件,也没有按 Key 的限流。真正生效的是上面的四项。不要把 scopes 当作安全边界,也不要依赖 rate_limit 控制成本。
认证与权限
管理 API Key 只能使用 JWT 用户会话,API Key 本身不能被用来创建、修改或删除 API Key。
| 权限码 | 允许的操作 |
|---|---|
apikey:read | 列出、查看 Key 与统计 |
apikey:create | 创建 Key |
apikey:update | 更新、激活、停用 Key |
apikey:delete | 删除 Key |
除权限码外还有归属校验:非超级管理员只能操作自己创建的 Key,否则返回 HTTP 403 / 3000(PERMISSION_DENIED)。超级管理员可查看并操作所有人的 Key,且绕过权限码检查。
端点总览
| 方法 | 路径 | 用途 | 所需权限 |
|---|---|---|---|
| GET | /api/v1/api-keys | 分页列出 API Key | apikey:read |
| GET | /api/v1/api-keys/stats | 聚合统计 | apikey:read |
| POST | /api/v1/api-keys | 创建 API Key | apikey:create |
| GET | /api/v1/api-keys/{api_key_id} | 查看单个 Key | apikey:read |
| PUT | /api/v1/api-keys/{api_key_id} | 更新 Key | apikey:update |
| DELETE | /api/v1/api-keys/{api_key_id} | 删除(吊销)Key | apikey:delete |
| POST | /api/v1/api-keys/{api_key_id}/activate | 激活 Key | apikey:update |
| POST | /api/v1/api-keys/{api_key_id}/deactivate | 停用 Key | apikey:update |
注意路由顺序:/stats 是固定路径,/{api_key_id} 需要 UUID,因此 /api/v1/api-keys/stats 不会被解析成 ID。
列出 API Key
GET /api/v1/api-keyscurl -X GET "https://your-domain.com/api/v1/api-keys?page=1&page_size=20" \
-H "Authorization: Bearer YOUR_TOKEN"| 查询参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
page | integer | 1 | 页码,从 1 开始 |
page_size | integer | 20 | 每页条数 |
status | string[] | — | 按状态过滤:active、inactive、expired(可重复,多个值取并集) |
user_id | UUID[] | — | 按所有者过滤;仅超级管理员生效,普通用户传了也会被忽略 |
search | string | — | 名称或密钥前缀的模糊匹配(大小写不敏感) |
状态定义与直觉一致但互有重叠:
active:is_active = true且(expires_at为空或尚未到期)。inactive:is_active = false(无论是否过期)。expired:expires_at已过(即使is_active仍为true)。
因此一个被停用且已过期的 Key 会同时出现在 inactive 与 expired 结果中,统计数字相加可能大于总数。
非超级管理员永远只看到自己的 Key;结果按创建时间倒序排列。
成功响应(200 OK):
{
"code": 0,
"data": {
"items": [
{
"id": "8f14e45f-ceea-467a-9d0f-1a2b3c4d5e6f",
"name": "Production API Key",
"key_prefix": "clou_a1b2c3d",
"user_id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
"user": { "id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c", "username": "alice" },
"scopes": ["chat"],
"rate_limit": 1000,
"is_active": true,
"last_used_at": "2026-09-24T14:30:00Z",
"expires_at": "2027-02-11T00:00:00Z",
"agents": [{ "id": "0b1c2d3e-...", "name": "Customer Support", "icon": "🤖" }],
"workflows": [],
"created_at": "2026-02-11T10:00:00Z",
"updated_at": "2026-02-11T10:00:00Z"
}
],
"total": 1,
"page": 1,
"page_size": 20
},
"msg": "success"
}user 仅在能够加载所有者时出现;last_used_at 在每次通过该 Key 成功认证后刷新。
查看单个 API Key
GET /api/v1/api-keys/{api_key_id}| 路径参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
api_key_id | string (UUID) | 是 | API Key 的 UUID |
响应体与列表项结构一致(不包含 key)。Key 不存在时返回 HTTP 404 / 4000;存在但不属于当前用户时返回 HTTP 403 / 3000。
创建 API Key
POST /api/v1/api-keys| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
name | string | 是 | — | 名称,长度 1–100 |
scopes | string[] | 否 | ["chat"] | 权限作用域列表(仅存储,不强制) |
rate_limit | integer | 否 | 1000 | 每分钟请求数,0 表示不限制(仅存储,不强制);必须 ≥ 0 |
expires_at | string (ISO 8601) | 否 | null | 过期时间;不传或传 null 表示永不过期 |
agent_ids | UUID[] | 否 | [] | 允许访问的 Agent;空数组表示不限制 |
workflow_ids | UUID[] | 否 | [] | 允许访问的 Workflow;空数组表示不限制 |
curl -X POST "https://your-domain.com/api/v1/api-keys" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Production API Key",
"expires_at": "2027-02-11T00:00:00Z"
}'绑定校验发生在创建时:agent_ids / workflow_ids 中的每一项都会经过 Agent/Workflow 访问检查,指定不存在或无权访问的资源会直接失败(HTTP 404 / 4000 或 403 / 3000)。因此绑定同时也是一次授权校验。
成功响应(200 OK):
{
"code": 0,
"data": {
"id": "8f14e45f-ceea-467a-9d0f-1a2b3c4d5e6f",
"name": "Production API Key",
"key": "clou_a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890",
"key_prefix": "clou_a1b2c3d",
"user_id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
"scopes": ["chat"],
"rate_limit": 1000,
"is_active": true,
"expires_at": "2027-02-11T00:00:00Z",
"last_used_at": null,
"agents": [],
"workflows": [],
"created_at": "2026-02-11T16:00:00Z",
"updated_at": "2026-02-11T16:00:00Z"
},
"msg": "API key created successfully"
}data.key 只在这一次响应中出现。服务端只保存哈希,任何后续查询都只能拿到 key_prefix。请立即写入密钥管理器或环境变量;丢失后只能删除并重新创建。
更新 API Key
PUT /api/v1/api-keys/{api_key_id}所有字段可选,只传需要变更的字段。未传的字段保持原值。
| 字段 | 类型 | 说明 |
|---|---|---|
name | string | 名称,长度 1–100 |
scopes | string[] | 覆盖作用域列表 |
rate_limit | integer | 覆盖速率限制值,必须 ≥ 0 |
expires_at | string (ISO 8601) | 覆盖过期时间 |
is_active | boolean | 直接切换启用状态 |
agent_ids | UUID[] | 整体替换 Agent 绑定;传 [] 清空绑定(清空后不限制任何 Agent) |
workflow_ids | UUID[] | 整体替换 Workflow 绑定;传 [] 清空绑定 |
agent_ids / workflow_ids 是替换语义,不是增量追加。要做到「只加一个」必须先把现有绑定读出来(GET 该 Key)再一起提交,否则会静默丢弃其余绑定。传 null 或不传字段才表示保持不变。
响应返回更新后的完整对象,msg 为 API key updated successfully。
删除 API Key
DELETE /api/v1/api-keys/{api_key_id}删除后立即生效:该 Key 不再能通过认证(后续请求返回 HTTP 401 / 2001)。响应中 data 是被删除的 Key 对象(用于审计回显),不是 null。
删除不可恢复,也没有「软删除/回收期」。轮换密钥时先创建新 Key 并确认可用,再删除旧 Key。
激活与停用
POST /api/v1/api-keys/{api_key_id}/activate
POST /api/v1/api-keys/{api_key_id}/deactivate两个端点都返回更新后的 Key 对象。状态不变时会报错,可用于检测并发修改:
- 对已激活的 Key 调用
activate:HTTP400/1002(api_key_already_active)。 - 对已停用的 Key 调用
deactivate:HTTP400/1002(api_key_already_inactive)。
停用是可逆的暂停:Key 记录与绑定都保留,重新激活后立即恢复原有访问范围。
统计
GET /api/v1/api-keys/stats普通用户统计自己的 Key,超级管理员统计全库。
{
"code": 0,
"data": { "total": 3, "active": 2, "inactive": 0, "expired": 1 },
"msg": "success"
}字段口径与列表的 status 过滤器相同:active 需要同时满足激活与未过期,inactive 只看激活位,expired 只看过期时间。由于口径重叠,四项之和不必然等于 total。
错误码
| HTTP | code | 场景 |
|---|---|---|
400 | 1001 | 请求体校验失败(如名称为空、rate_limit 为负) |
400 | 1002 | 目录已处于目标状态(重复激活/停用) |
401 | 2000 | 未提供认证信息 |
401 | 2001 | API Key 无效、已删除或已停用;JWT 无效或已吊销 |
401 | 2002 | API Key 已过期 |
401 | 2004 | Key 所属用户已停用或待审批 |
403 | 2003 | JWT 无法解析或已过期 |
403 | 3000 | 缺少 apikey:* 权限,或操作他人的 Key,或 Key 绑定不匹配 |
404 | 4000 | api_key_id 不存在 |
作用域取值
scopes 是自由字符串数组,服务端不做枚举校验,也不做任何强制。为便于后续版本启用,社区与界面中常用的取值如下:
| 取值 | 含义 |
|---|---|
chat | 调用 Agent 对话端点(创建时的默认值) |
agent:read | 读取 Agent 信息 |
agent:chat | 与 Agent 对话 |
workflow:execute | 执行工作流 |
workflow:run | 触发工作流运行 |
kb:read | 读取知识库内容 |
如果你需要「最小权限」,当前唯一可靠的机制是资源绑定:把 agent_ids / workflow_ids 限定到实际要调用的资源,并在不需要时停用 Key。作用域字符串只能作为文档与审计线索。
最佳实践
安全
- 把完整密钥存进环境变量或密钥管理器,不要提交到版本库、写进日志或前端代码。
- 一个集成一个 Key,便于单独停用而不影响其他调用方。
- 为生产 Key 设置
expires_at,并为轮换留出重叠期。 - 用
agent_ids/workflow_ids把 Key 限制在实际需要的资源上。 - 定期检查
last_used_at;长期未使用的 Key 应删除。 - 通过
DELETE(彻底吊销)或deactivate(临时暂停)响应泄露事件。
生命周期管理
- 名称写清用途与环境,例如
prod-chatbot-gateway。 - 轮换流程:创建新 Key → 更新调用方配置 → 观察
last_used_at停止更新 → 删除旧 Key。 - 需要临时排查时优先用
deactivate,恢复成本低于重新分发新密钥。 - 团队权限调整后复核 Key:Key 继承所有者身份,所有者离开团队会影响 Key 的可访问范围。
代码示例
Python
import os
import requests
BASE = "https://your-domain.com/api/v1"
def create_api_key(token: str, name: str, agent_ids: list[str] | None = None) -> dict:
"""创建一个绑定到指定 Agent 的 API Key。"""
response = requests.post(
f"{BASE}/api-keys",
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
json={
"name": name,
"expires_at": "2027-02-11T00:00:00Z",
"agent_ids": agent_ids or [],
},
)
result = response.json()
if result["code"] != 0:
raise RuntimeError(f"create failed: {result['code']} {result['msg']}")
# data["key"] 只出现这一次
print(f"created {result['data']['key_prefix']}…")
return result["data"]
def list_api_keys(token: str, status: str = "active") -> dict:
"""按状态分页查询自己的 API Key。"""
response = requests.get(
f"{BASE}/api-keys",
headers={"Authorization": f"Bearer {token}"},
params={"page": 1, "page_size": 20, "status": status},
)
return response.json()["data"]
# 使用
token = os.environ["USER_TOKEN"]
new_key = create_api_key(token, "prod-chatbot")
print("把完整密钥写入密钥管理器:", new_key["key"])JavaScript
const BASE = 'https://your-domain.com/api/v1';
async function createApiKey(token, name, agentIds = []) {
const response = await fetch(`${BASE}/api-keys`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name,
expires_at: '2027-02-11T00:00:00Z',
agent_ids: agentIds,
}),
});
const result = await response.json();
if (result.code !== 0) {
throw new Error(`create failed: ${result.code} ${result.msg}`);
}
return result.data; // data.key 只出现这一次
}
async function deactivateApiKey(token, keyId) {
const response = await fetch(`${BASE}/api-keys/${keyId}/deactivate`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
});
const result = await response.json();
if (result.code !== 0) {
throw new Error(`deactivate failed: ${result.code} ${result.msg}`);
}
return result.data;
}
// 使用
const token = process.env.USER_TOKEN;
const created = await createApiKey(token, 'prod-chatbot');
console.log('完整密钥只在本次响应中可用:', created.key);相关文档
- API 快速开始:拿到 Key 之后发送第一个请求
- 认证与用户登录 API:获取可管理 API Key 的 JWT 会话
- 错误处理:
2001、2002、3000的统一处理建议
这篇文章对你有帮助吗?