OAuth 2.0
小雅开放平台使用授权码流程,让第三方智能体获得当前用户明确授权的最小权限。OAuth 只是授权层;实际访问 Personal 或 Team 资源时,服务端仍会重新校验用户、智能体、Scope、Team 成员状态和上下文版本。
授权入口
GET /oauth/authorize常用参数:
| 参数 | 说明 |
|---|---|
response_type=code | 当前只使用授权码响应 |
client_id | 开放平台登记的客户端 ID |
redirect_uri | 必须与登记的精确地址一致 |
scope | 空格分隔的最小权限集合 |
state | 主动浏览器授权流程必须生成并校验 |
code_challenge | 公共客户端使用 PKCE S256 |
code_challenge_method=S256 | 不接受弱化的 PKCE 算法 |
team_id | Team 授权时显式指定精确 Team |
完整浏览器跳转示例:
https://claw.zhenhuiai.com/oauth/authorize?response_type=code&client_id=agent-client&redirect_uri=https%3A%2F%2Fagent.example.com%2Foauth%2Fcallback&scope=profile%20balance%20model&state=<random-state>&code_challenge=<s256-challenge>&code_challenge_method=S256授权成功后,平台重定向至登记回调:
https://agent.example.com/oauth/callback?code=<single-use-code>&state=<same-state>小雅 App 的“点击打开”流程会直接生成一次性 Code 并附加到已登记入口。Code 只有 5 分钟有效且只能消费一次。回调页面在交换完成前不得渲染第三方资源,并应立即 303 跳转到不含 Code 的干净 URL。
Scope
| Scope | 能力 |
|---|---|
profile | 当前授权用户以及可见 Team 上下文 |
balance | Personal 或精确 Team 余额 |
charge | 主动扣费与退款 |
usage | 当前智能体在 Personal 上下文的归因用量 |
model | Personal 或 Team 可用模型目录与模型网关 |
team_read | Team 只读访问 |
team_manage | Team Owner/Admin 管理能力 |
skill | 已授权运行时 Skill/MCP 资产 |
sandbox | 云沙箱运行时 |
默认 Scope 仍是:
profile balance charge usage默认值不包含 model、team_read 或 team_manage。需要模型或 Team 的客户端必须显式申请,不能依赖默认值。
Token 生命周期
- 授权码:5 分钟、单次消费;
- 新授权码交换的 Access Token:7 天;已有 Token 保持原到期时间,开发者会话不得超过返回的
expires_in; - 新流程不签发 Refresh Token;
- 历史 Refresh Token 只作为兼容路径继续轮换;
- 用户、智能体、成员、角色、Team 状态或上下文版本变化会使旧凭证失效。
交换 Access Token
机密服务端客户端使用 client_secret;公共客户端不保存 Secret,必须提交创建授权请求时对应的 code_verifier。
curl --request POST 'https://claw.zhenhuiai.com/oauth/token' \
--header 'Accept: application/json' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'client_id=agent-client' \
--data-urlencode 'client_secret=<server-only-secret>' \
--data-urlencode 'code=<single-use-code>' \
--data-urlencode 'redirect_uri=https://agent.example.com/oauth/callback'Node.js 服务端示例:
const body = new URLSearchParams({
grant_type: 'authorization_code',
client_id: process.env.XIAOYA_CLIENT_ID,
client_secret: process.env.XIAOYA_CLIENT_SECRET,
code,
redirect_uri: 'https://agent.example.com/oauth/callback'
});
const response = await fetch('https://claw.zhenhuiai.com/oauth/token', {
method: 'POST',
headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },
body,
redirect: 'error',
signal: AbortSignal.timeout(10_000)
});
const token = await response.json();
if (!response.ok) throw new Error(`OAuth failed: ${token.error}`);Personal Token 成功响应:
{
"access_token": "<server-only-token>",
"token_type": "Bearer",
"expires_in": 604800,
"scope": "profile balance charge usage model"
}Team Token 还会返回绑定信息:
{
"access_token": "<server-only-team-token>",
"token_type": "Bearer",
"expires_in": 604800,
"scope": "profile balance model team_read",
"team_id": "team_01",
"team_authorization_id": "authorization_01"
}| 字段 | 含义 |
|---|---|
access_token | 仅可信服务端保存并放入 Bearer Header 的凭证 |
token_type | 固定为 Bearer |
expires_in | 从签发时刻起的有效秒数,当前为 604800 |
scope | 实际获准的空格分隔 Scope;不能假设等于申请值 |
team_id | 仅 Team Token 返回,表示唯一绑定 Team |
team_authorization_id | 仅 Team Token 返回,表示可撤销的 Team 授权记录 |
失败响应遵循 OAuth 错误格式,不使用平台 success/data envelope:
{
"error": "invalid_grant",
"error_description": "Authorization code is invalid or expired"
}常见 error 为 invalid_client、invalid_grant 和 unsupported_grant_type。不要向最终用户展示包含凭据或内部细节的原始错误描述。
撤销 Token
退出登录、解除绑定或检测到服务端会话风险时撤销 Token:
curl --request POST 'https://claw.zhenhuiai.com/oauth/revoke' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=agent-client' \
--data-urlencode 'client_secret=<server-only-secret>' \
--data-urlencode 'token=<access-token>' \
--data-urlencode 'token_type_hint=access_token'成功撤销返回 HTTP 200;即使 Token 已失效,客户端也应清除自己的服务端 Session,不得继续尝试使用旧 Token。
Personal 与 Team 隔离
Personal Token 与 Team Token 必须分别保存,并至少按以下维度建立服务端会话键:
client_id + user_subject + context_type + team_idTeam Token 绑定精确 Team,不能访问另一个 Team;Team Token 也不能调用 Personal 余额或用量接口。Personal Token 访问 Team 受保护资源会被拒绝。
服务端安全要求
- Access Token、Client Secret 和 Code 只保存在可信服务端;
- 浏览器只保存智能体自己的
HttpOnly; Secure; SameSite=LaxSession Cookie; - Token 请求设置明确超时并禁止重定向;
- Code 交换结果未知时不要盲目重试,重新发起授权;
- 回调和 Token 响应使用
Cache-Control: no-store; - 不把 Token、Code 或秘密放进 URL、Prompt、日志、错误详情和前端存储。
全部 OAuth 参数与响应 Schema 见完整 API 参考。
