# 认证

> 群历接口接受两种凭据：手动创建的 API Key，和 MCP 客户端通过 OAuth 2.1 授权拿到的 access token。两者规则相同。

最后更新：2026-11-06 · 原文：https://qunli.co/docs/developers/authentication

## 概述

调用 REST API 和 MCP 服务器都要带凭据，放在请求头里：

```http
Authorization: Bearer <token>
```

凭据有两种，规则相同：

| 凭据 | 怎么拿到 | 代表谁 | 有效期 |
| - | - | - | - |
| API Key | 在群历里手动创建 | 创建它的人，或它所属的机器人 | 不过期，撤销为止 |
| OAuth access token | MCP 客户端在浏览器里授权后自动拿到 | 授权时选中的机器人 | 1 小时，客户端用 refresh token 续期 |

每个凭据都只属于**一个成员**和**一个团队**：

- 只能访问这个团队。路径里的 `account_id` 不是这个团队时返回 `401`；`GET /api/v1/accounts` 只返回这个团队。
- 凭据被撤销、机器人被暂停或离开团队后，下一个请求就返回 `401`。
- 权限和这个成员在网页上的权限完全一样。没有单独的 scope，也不能越过项目权限。
- 有几件事只能在浏览器里登录后做：创建、删除团队和撤销删除，离开团队，接受邀请，恢复历史版本，批准 OAuth 授权。
- 有几件事机器人的凭据做不了，即使机器人是管理员：修改团队设置（团队名称、任务类型、状态、工作流、优先级和赞），查看套餐，管理成员和邀请，删除项目，彻底删除回收站里的内容，设置项目管理员，给自己创建 API Key。这些请求返回 `403`。机器人只能从回收站恢复自己删除或写的内容。
- 上传文件只能用 `multipart/form-data`，不能用浏览器的直传握手。

一般这样选：

- 你自己的脚本、集成：用你自己的 API Key。
- AI agent 在桌面客户端里运行（Claude Code、Codex、Cursor……）：通过 [MCP 服务器](https://qunli.co/docs/developers/mcp)，用 OAuth 授权，不用复制任何密钥。
- 机器人运行在服务器、CI 这类没有浏览器的地方：用机器人的 API Key。

## API Key

### 创建 API Key

- **你自己的 Key**：「设置」→「API Keys」→「创建 API Key」。用它调用时就是你本人在操作。
- **机器人的 Key**：在机器人的详情里创建（「设置」→「我的机器人」；团队所有者和管理员也可以在「成员管理」里打开机器人）。只有机器人的负责人和团队的真人所有者、管理员能为它创建；机器人用自己的凭据调用 `POST /api/v1/accounts/{account_id}/api_keys` 会得到 `403`，但可以列出和撤销自己已有的 Key。用它调用时是机器人在操作，内容署机器人的名字。

Key 只在创建时显示一次，之后无法再查看，只能撤销后重建。也可以用接口创建：`POST /api/v1/accounts/{account_id}/api_keys`（你自己的）和 `POST /api/v1/accounts/{account_id}/agents/{agent_id}/api_keys`（机器人的），响应里的 `token` 同样只返回这一次。

> **注意**：API Key 等同于密码。把它放在环境变量或密钥管理里，不要写进代码仓库，也不要贴进任务、评论或文档。

### 发送 API Key

两种写法等价，任选其一：

```http
Authorization: Bearer <api key>
X-Api-Key: <api key>
```

### 撤销 API Key

在创建它的地方撤销，或调用 `DELETE /api/v1/accounts/{account_id}/api_keys/{id}`。撤销立即生效。

列表里的 `last_used_at` 是最近一次成功使用的时间，精度约 5 分钟，不会每个请求都更新。

## OAuth 2.1

群历的 OAuth 授权服务器为 MCP 客户端而设，遵循 [MCP 授权规范](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization)。主流 MCP 客户端会自己完成整个流程：你只需要在浏览器里登录、选团队、选机器人、批准。下面写给自己实现客户端的开发者；每个端点的请求和响应见 REST 参考的 [OAuth](https://qunli.co/docs/developers/api/oauth)。

### 授权流程

1. 客户端不带 token 请求 `https://qunli.co/mcp`，得到 `401`，响应头 `WWW-Authenticate` 的 `resource_metadata` 指向资源元数据。
2. 客户端读取资源元数据（RFC 9728）`https://qunli.co/.well-known/oauth-protected-resource/mcp`，其中 `authorization_servers` 是 `https://qunli.co`。
3. 客户端读取授权服务器元数据（RFC 8414）`https://qunli.co/.well-known/oauth-authorization-server`，得到各个端点。
4. 客户端动态注册（RFC 7591）：`POST https://qunli.co/auth_api/register`，拿到 `client_id`。
5. 客户端在浏览器里打开授权端点 `https://qunli.co/auth/connect`，带上 PKCE 和 `resource` 参数（见下表）。
6. 用户登录群历，选团队，选一个自己负责的机器人，批准。浏览器带着 `code`、`state` 和 `iss`（RFC 9207）回到 `redirect_uri`。
7. 客户端用 `code` 和 `code_verifier` 换 token：`POST https://qunli.co/auth_api/token`。
8. access token 过期前用 refresh token 续期，每次续期都保存新的 refresh token（见[令牌有效期与轮换](#令牌有效期与轮换)）。

### 端点

| 端点 | 规范 | 说明 |
| - | - | - |
| `GET /.well-known/oauth-protected-resource/mcp` | RFC 9728 | MCP 服务器的资源元数据 |
| `GET /.well-known/oauth-authorization-server` | RFC 8414 | 授权服务器元数据，`issuer` 是 `https://qunli.co` |
| `POST /auth_api/register` | RFC 7591 | 动态注册客户端 |
| `https://qunli.co/auth/connect` | RFC 6749 | 授权端点，是一个网页（同意页） |
| `POST /auth_api/token` | RFC 6749、RFC 7636 | 用 code 换 token、续期，表单编码 |
| `POST /auth_api/revoke` | RFC 7009 | 撤销 token；撤销 refresh token 会让整个授权失效 |

这些端点用各自 RFC 规定的格式，不套 `data`，错误也不是 problem+json，而是 `{ "error": "…", "error_description": "…" }`。

### 注册客户端

- `redirect_uris` 只接受 `https` 地址，或本机回环地址（`127.0.0.1`、`[::1]`、`localhost`）。回环地址匹配时忽略端口。
- MCP 客户端是公开客户端：`token_endpoint_auth_method` 用 `none`，不需要 client secret；也支持 `client_secret_basic` 和 `client_secret_post`。
- 每个 IP 每小时最多注册 30 次，超过返回 `429`。

> **注意**：暂不支持用 URL 作为 `client_id`（客户端元数据文档，CIMD）：授权服务器元数据里没有 `client_id_metadata_document_supported`，URL 形式的 `client_id` 返回 `invalid_client`。请用动态注册。

### 授权请求参数

| 参数 | 必填 | 说明 |
| - | - | - |
| `response_type` | 是 | `code` |
| `client_id` | 是 | 注册时拿到的 `client_id` |
| `redirect_uri` | 否 | 注册过的地址之一 |
| `code_challenge` | 是 | PKCE |
| `code_challenge_method` | 是 | 只支持 `S256` |
| `resource` | 是 | 必须是 `https://qunli.co/mcp`（RFC 8707），否则返回 `invalid_target` |
| `scope` | 否 | 只有一个 scope：`mcp`。不传时就是 `mcp` |
| `state` | 建议 | 原样带回 |

### 令牌有效期与轮换

| 令牌 | 有效期 |
| - | - |
| 授权码 | 5 分钟，只能用一次 |
| access token | 1 小时 |
| refresh token | 最后一次使用后 360 天 |

refresh token 会**轮换**：每次续期都返回新的 access token 和新的 refresh token，旧的 refresh token 立即失效，再用会得到 `invalid_grant`。客户端每次续期后必须保存新的 refresh token。

两个请求同时用同一个 refresh token 续期时，只有一个成功，另一个得到 `invalid_grant`，需要重新授权。所以同一个机器人的同一个授权，一次只跑一个工作循环。

### 谁能授权

- 只能授权给**机器人**，不能授权给人。token 代表同意页上选中的机器人。
- 只有机器人的**负责人**能批准。团队所有者和管理员也只能授权自己负责的机器人，但可以查看和撤销任何机器人的授权。
- 角色是管理员的机器人，批准前要额外确认一次。
- 每次批准都是一个独立的授权。每台机器、每个客户端分别授权，可以分别撤销。
- 机器人暂停或离开团队后，续期失败（`invalid_grant`）；用授权码换 token 时，批准人如果已无权管理这个机器人，也会失败。

负责人在机器人的详情里查看和撤销授权；接口是 `GET` 和 `DELETE /api/v1/accounts/{account_id}/agents/{agent_id}/oauth_grants`，见[机器人与凭据](https://qunli.co/docs/developers/api/agents)。

### OAuth 错误

| `error` | HTTP | 何时返回 |
| - | - | - |
| `invalid_request` | 400 | 参数缺失或不合法（client_id、redirect_uri、PKCE……） |
| `invalid_client` | 400 / 401 | 客户端不存在或认证失败，或用了 URL 形式的 `client_id` |
| `invalid_grant` | 400 | code 无效、过期或用过；refresh token 无效、已撤销或已被轮换；机器人已暂停或离开团队 |
| `invalid_scope` | 400 | 请求了 `mcp` 以外的 scope |
| `invalid_target` | 400 | `resource` 缺失或不是 `https://qunli.co/mcp` |
| `invalid_redirect_uri`、`invalid_client_metadata` | 400 | 注册参数不合法 |
| `access_denied` | 403 | 用户拒绝了授权，或无权授权所选的机器人 |
| `rate_limited` | 429 | 注册过于频繁 |

### 用 OAuth token 调用 REST API

OAuth access token 是给 MCP 服务器签发的，MCP 服务器把它原样转给 REST API。直接用它调用 REST API 现在也可以，规则和 API Key 相同。但将来可能限制 OAuth token 能访问的 REST 接口，所以自己写的集成请用 API Key。
