ClouisleClouisle

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 认证实际会校验四件事,任何一项不满足都会被拒绝:

  1. 激活状态:is_active = false 的 Key 不再参与匹配,返回 HTTP 401 / 2001(INVALID_TOKEN)。
  2. 过期时间:expires_at 非空且已过期时返回 HTTP 401 / 2002(TOKEN_EXPIRED);留空表示永不过期。
  3. 所有者账号状态:Key 所属用户被停用或待审批时返回 HTTP 401 / 2004(INACTIVE_USER)。
  4. 资源绑定:当 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 Keyapikey:read
GET/api/v1/api-keys/stats聚合统计apikey:read
POST/api/v1/api-keys创建 API Keyapikey:create
GET/api/v1/api-keys/{api_key_id}查看单个 Keyapikey:read
PUT/api/v1/api-keys/{api_key_id}更新 Keyapikey:update
DELETE/api/v1/api-keys/{api_key_id}删除(吊销)Keyapikey:delete
POST/api/v1/api-keys/{api_key_id}/activate激活 Keyapikey:update
POST/api/v1/api-keys/{api_key_id}/deactivate停用 Keyapikey:update

注意路由顺序:/stats 是固定路径,/{api_key_id} 需要 UUID,因此 /api/v1/api-keys/stats 不会被解析成 ID。

列出 API Key

GET /api/v1/api-keys
curl -X GET "https://your-domain.com/api/v1/api-keys?page=1&page_size=20" \
  -H "Authorization: Bearer YOUR_TOKEN"
查询参数类型默认值说明
pageinteger1页码,从 1 开始
page_sizeinteger20每页条数
statusstring[]—按状态过滤:active、inactive、expired(可重复,多个值取并集)
user_idUUID[]—按所有者过滤;仅超级管理员生效,普通用户传了也会被忽略
searchstring—名称或密钥前缀的模糊匹配(大小写不敏感)

状态定义与直觉一致但互有重叠:

  • 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_idstring (UUID)是API Key 的 UUID

响应体与列表项结构一致(不包含 key)。Key 不存在时返回 HTTP 404 / 4000;存在但不属于当前用户时返回 HTTP 403 / 3000。

创建 API Key

POST /api/v1/api-keys
字段类型必填默认值说明
namestring是—名称,长度 1–100
scopesstring[]否["chat"]权限作用域列表(仅存储,不强制)
rate_limitinteger否1000每分钟请求数,0 表示不限制(仅存储,不强制);必须 ≥ 0
expires_atstring (ISO 8601)否null过期时间;不传或传 null 表示永不过期
agent_idsUUID[]否[]允许访问的 Agent;空数组表示不限制
workflow_idsUUID[]否[]允许访问的 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}

所有字段可选,只传需要变更的字段。未传的字段保持原值。

字段类型说明
namestring名称,长度 1–100
scopesstring[]覆盖作用域列表
rate_limitinteger覆盖速率限制值,必须 ≥ 0
expires_atstring (ISO 8601)覆盖过期时间
is_activeboolean直接切换启用状态
agent_idsUUID[]整体替换 Agent 绑定;传 [] 清空绑定(清空后不限制任何 Agent)
workflow_idsUUID[]整体替换 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:HTTP 400 / 1002(api_key_already_active)。
  • 对已停用的 Key 调用 deactivate:HTTP 400 / 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。

错误码

HTTPcode场景
4001001请求体校验失败(如名称为空、rate_limit 为负)
4001002目录已处于目标状态(重复激活/停用)
4012000未提供认证信息
4012001API Key 无效、已删除或已停用;JWT 无效或已吊销
4012002API Key 已过期
4012004Key 所属用户已停用或待审批
4032003JWT 无法解析或已过期
4033000缺少 apikey:* 权限,或操作他人的 Key,或 Key 绑定不匹配
4044000api_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);

相关文档

这篇文章对你有帮助吗?

本页目录