洋葱头openAPI接口文档
更新日期:2026-04-17 15:04:38
Onion OpenAPI 接口文档
版本: 1.0.1 联系: Oray Inc. 服务器: https://openapi.yctou.net/openapi
目录
认证方式
概述
本 API 采用两种认证方式:
| 接口类型 | 认证方式 | 说明 |
|---|---|---|
/token |
API Key + 签名验证 | 使用 API Key 生成访问 Token |
/v1/* |
Bearer Token | 使用 access_token 访问业务接口 |
API Key + 签名验证(仅 /token 接口)
用于获取访问 Token 接口 (/token)。
请求头参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| X-Signature | string | 是 | HMAC-SHA256 签名 |
| X-Timestamp | string | 是 | Unix 时间戳(秒级) |
Query 参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| api_key | string | 是 | API Key ID |
签名算法
签名仅需要 api_key 和 timestamp 两个参数。
签名生成步骤
- 准备参数
api_key: 你的 API Key IDtimestamp: 当前 Unix 时间戳(秒级),如"1711234567"secret: 你的 API Key Secret(用于生成签名的密钥)
- 拼接签名字符串
按字母顺序(ASCII)拼接:
api_key=<api_key>×tamp=<timestamp>sign_string = "api_key=your_api_key_id×tamp=1711234567" - 计算 HMAC-SHA256 签名
使用
secret作为密钥,对sign_string进行 HMAC-SHA256 签名:然后将签名结果转换为十六进制字符串。signature = HMAC-SHA256(secret, sign_string)
签名验证注意事项
| 注意事项 | 说明 |
|---|---|
| 时间戳容差 | 时间戳有效期为 5 分钟(300秒),超出将返回 1201003 错误 |
| 字母排序 | 参数排序使用英文字母顺序(ASCII),区分大小写 |
| 参数编码 | 参数值保持原始字符串,不需要 URL 编码 |
| 签名参数 | 只需要 api_key 和 timestamp 两个参数 |
常见签名错误
| 错误码 | 错误内容 | 原因 |
|---|---|---|
| 1201001 | 缺少签名或时间戳 | 未提供 signature 或 timestamp 参数 |
| 1201002 | 无效时间戳格式 | timestamp 不是有效的 Unix 时间戳 |
| 1201003 | 时间戳已过期 | timestamp 超出 5 分钟容差 |
| 1201004 | 缺少 API Key | 缺少 api_key 参数 |
| 1201005 | 无效 API Key | API Key 不存在或已禁用 |
| 1201006 | 无效签名 | 签名计算结果不匹配 |
代码示例
JavaScript / Node.js
const crypto = require('crypto');
/**
* 生成 OpenAPI 签名
* @param {string} apiKey - API Key ID
* @param {string} secret - API Key Secret
* @returns {object} - 包含 timestamp, signature, api_key 的对象
*/
function generateSignature(apiKey, secret) {
const timestamp = String(Math.floor(Date.now() / 1000));
const signStr = `api_key=${apiKey}×tamp=${timestamp}`;
const mac = crypto.createHmac('sha256', secret);
mac.update(signStr);
const signature = mac.digest('hex');
return {
timestamp,
signature,
api_key: apiKey
};
}
// 示例
const apiKey = 'your_api_key_id';
const secret = 'your_api_key_secret';
const { timestamp, signature, api_key } = generateSignature(apiKey, secret);
console.log(`timestamp=${timestamp}`);
console.log(`signature=${signature}`);
console.log(`api_key=${api_key}`);
Golang
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"strconv"
"time"
)
// GenerateSignature 生成签名
func GenerateSignature(apiKey, secret string) (timestamp, signature, apiKeyOut string) {
timestamp = strconv.FormatInt(time.Now().Unix(), 10)
signStr := fmt.Sprintf("api_key=%s×tamp=%s", apiKey, timestamp)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(signStr))
signature = hex.EncodeToString(mac.Sum(nil))
return timestamp, signature, apiKey
}
func main() {
apiKey := "your_api_key_id"
secret := "your_api_key_secret"
timestamp, signature, apiKeyOut := GenerateSignature(apiKey, secret)
fmt.Printf("timestamp=%s\n", timestamp)
fmt.Printf("signature=%s\n", signature)
fmt.Printf("api_key=%s\n", apiKeyOut)
}
Python
import hmac
import hashlib
import time
def generate_signature(api_key, secret):
"""
生成 OpenAPI 签名
@param api_key: API Key ID
@param secret: API Key Secret
@return: dict 包含 timestamp, signature, api_key
"""
timestamp = str(int(time.time()))
sign_str = f"api_key={api_key}×tamp={timestamp}"
signature = hmac.new(
secret.encode('utf-8'),
sign_str.encode('utf-8'),
hashlib.sha256
).hexdigest()
return {
'timestamp': timestamp,
'signature': signature,
'api_key': api_key
}
# 示例
api_key = 'your_api_key_id'
secret = 'your_api_key_secret'
result = generate_signature(api_key, secret)
print(f"timestamp={result['timestamp']}")
print(f"signature={result['signature']}")
print(f"api_key={result['api_key']}")
##### 完整调用示例
使用签名参数发送 POST 请求获取 Token:
```bash
# 1. 生成签名
timestamp=1711234567
signature=abc123def456...
api_key=your_api_key_id
# 2. 发送请求(api_key 在 query 参数中)
curl -X POST "https://openapi.yctou.net/openapi/token?api_key=your_api_key_id" \
-H "X-Timestamp: ${timestamp}" \
-H "X-Signature: ${signature}"
// Node.js 完整示例
const crypto = require('crypto');
const https = require('https');
function generateSignature(apiKey, secret) {
const timestamp = String(Math.floor(Date.now() / 1000));
const signStr = `api_key=${apiKey}×tamp=${timestamp}`;
const mac = crypto.createHmac('sha256', secret);
mac.update(signStr);
const signature = mac.digest('hex');
return { timestamp, signature, api_key: apiKey };
}
async function getToken(apiKey, secret) {
const { timestamp, signature, api_key } = generateSignature(apiKey, secret);
const url = new URL('https://openapi.yctou.net/openapi/token');
url.searchParams.append('api_key', api_key);
const options = {
hostname: 'openapi.yctou.net',
path: '/openapi/token',
method: 'POST',
headers: {
'X-Timestamp': timestamp,
'X-Signature': signature
}
};
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => data += chunk);
res.on('end', () => resolve(JSON.parse(data)));
});
req.on('error', reject);
req.end();
});
}
// 使用
const apiKey = 'your_api_key_id';
const secret = 'your_api_key_secret';
getToken(apiKey, secret).then(console.log);
### Bearer Token 认证
用于所有业务接口 (`/v1/*`)。
获取 Token 后,使用 Bearer Token 方式访问其他接口。
**请求头**
Authorization: Bearer <access_token>
**示例**
```bash
curl -X GET "https://openapi.yctou.net/openapi/v1/vault?page=1&size=10" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
错误码说明
响应格式
所有接口的响应都遵循以下统一格式:
{
"code": 0,
"message": "success",
"data": {}
}
错误码分类
| 错误类型 | 说明 |
|---|---|
| 业务错误 | 参数校验、数据验证等业务逻辑错误 |
| 数据库错误 | 数据库操作失败 |
| 权限错误 | 认证失败、无权限访问等 |
| 系统错误 | 系统内部错误、服务不可用等 |
通用错误码
| 错误码常量 | 值 | 说明 | HTTP状态码 |
|---|---|---|---|
| CodeSysOk | 0 | 请求成功 | 200 |
| CodeSysFail | -1 | 系统操作错误 | 500 |
| CodeSysDataServerError | -3 | 数据服务异常 | 500 |
| CodeSysUserNoPermission | -4 | 无权限操作 | 403 |
| CodeSysInvalidParamsInput | -2 | 无效参数输入 | 400 |
| CodeUserUnauthorized | -17 | 用户未授权操作 | 401 |
OpenAPI 专用错误码
认证类错误码 (ModuleOpenAPIAuth)
| 错误码常量 | 值 | 说明 | HTTP状态码 |
|---|---|---|---|
| CodeOpenAPIMissingSignature | 1201001 | 缺少签名或时间戳 | 401 |
| CodeOpenAPIInvalidTimestamp | 1201002 | 无效时间戳格式 | 401 |
| CodeOpenAPITimestampExpired | 1201003 | 时间戳已过期 | 401 |
| CodeOpenAPIMissingApiKey | 1201004 | 缺少 API Key | 401 |
| CodeOpenAPIInvalidApiKey | 1201005 | 无效 API Key | 401 |
| CodeOpenAPIInvalidSignature | 1201006 | 无效签名 | 401 |
| CodeOpenAPIMissingToken | 1201007 | 缺少 Token | 401 |
| CodeOpenAPIInvalidToken | 1201008 | 无效 Token | 401 |
| CodeOpenAPIInvalidScope | 1201009 | 无效 scope | 403 |
| CodeOpenAPIMissingTeamId | 1201010 | Token 缺少 team id | 401 |
| CodeOpenAPITeamIdEmpty | 1201011 | teamId 为空 | 403 |
Vault 业务类错误码 (ModuleOpenAPIVault)
| 错误码常量 | 值 | 说明 | HTTP状态码 |
|---|---|---|---|
| CodeOpenAPIVaultAccountEmpty | 1202001 | 账号不能为空 | 400 |
| CodeOpenAPIVaultAliasEmpty | 1202002 | 别名不能为空 | 400 |
| CodeOpenAPIVaultAppIdsEmpty | 1202003 | 应用 ID 列表为空 | 400 |
| CodeOpenAPIVaultAppIdsInvalid | 1202004 | 应用 ID 不合法 | 400 |
| CodeOpenAPIVaultTeamIdInvalid | 1202005 | teamId 无效 | 400 |
| CodeOpenAPIVaultUnauthorizedApp | 1202006 | 无权访问指定应用 | 403 |
| CodeOpenAPIVaultUnauthorizedMember | 1202007 | 无权访问指定成员 | 403 |
| CodeOpenAPIVaultUnauthorizedGroup | 1202008 | 无权访问指定分组 | 403 |
接口列表
Token
POST /token - 生成 Token
使用 API Key 获取访问 Token。
认证方式: API Key + 签名验证
请求头
| 参数 | 说明 |
|---|---|
| X-Signature | HMAC-SHA256 签名 |
| X-Timestamp | Unix 时间戳(秒级) |
Query 参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| api_key | string | 是 | API Key ID |
请求示例
curl -X POST "https://openapi.yctou.net/openapi/token?api_key=your_api_key_id" \
-H "X-Timestamp: 1711234567" \
-H "X-Signature: abc123..."
成功响应 (200)
{
"code": 0,
"message": "success",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": 1711234567
}
}
错误响应 (401)
| 错误码 | 错误内容 | 错误原因 |
|---|---|---|
| 1201001 | 缺少签名或时间戳 | 缺少 X-Signature 或 X-Timestamp 请求头 |
| 1201002 | 无效时间戳格式 | 时间戳格式不正确,无法解析 |
| 1201003 | 时间戳已过期 | 时间戳超过 5 分钟容差 |
| 1201004 | 缺少 API Key | 缺少 api_key 参数 |
| 1201005 | 无效 API Key | API Key 不存在或已禁用 |
| 1201006 | 无效签名 | 签名验证失败 |
Vault
POST /v1/vault - 创建账号密码
创建一个新的账号密码记录。
认证方式: Bearer Token
请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| account | string | 是 | 账号,最大长度 50 |
| account_id | string | 否 | 账号ID |
| password | string | 否 | 密码,最大长度 50 |
| alias | string | 是 | 别名,最大长度 50 |
| team_application_ids | array[string] | 是 | 团队应用ID数组 |
| member_ids | array[string] | 否 | 成员ID数组(越权检查) |
| group_ids | array[string] | 否 | 分组ID数组(越权检查) |
| group_status_id | integer | 否 | 分组状态ID(0=未授权, 1=指定分组/成员, 2=授权全部成员) |
| category_ids | array[string] | 否 | 账号分类ID数组(越权检查,只允许 type_id=3 的分类) |
权限说明
- team_id 从 Token 中自动提取
- team_application_ids、member_ids、group_ids 会进行越权检查
- 只有属于当前团队的合法 ID 才会被写入
成功响应 (200)
{
"code": 0,
"message": "success",
"data": {
"key_id": "1234567890"
}
}
错误响应
| HTTP码 | 错误码 | 错误内容 | 错误原因 |
|---|---|---|---|
| 400 | 1202005 | teamId 无效 | teamId 为 0 |
| 400 | 1202001 | 账号不能为空 | account 字段为空 |
| 400 | 1202002 | 别名不能为空 | alias 字段为空 |
| 400 | 1202003 | 应用 ID 列表为空 | team_application_ids 数组为空 |
| 400 | 1202004 | 应用 ID 不合法 | 应用 ID 不属于当前团队 |
| 401 | 1201007 | 缺少 Token | 缺少 Authorization 请求头 |
| 401 | 1201008 | 无效 Token | Token 格式错误或已过期 |
| 401 | 1201010 | Token 缺少 team id | Token 中不包含 team id |
| 403 | 1201009 | 无效 scope | scope 不为 "openapi" |
| 403 | 1201011 | teamId 为空 | Token 中 team id 为 0 |
| 500 | -3 | 数据服务异常 | 数据库操作失败 |
GET /v1/vault - 获取账号密码列表
获取当前团队下的账号密码列表(全局列表,不按应用过滤)。
认证方式: Bearer Token
请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page | integer | 是 | 页码,最小值 1 |
| size | integer | 是 | 每页数量,最小值 1,最大值 100 |
| keyword | string | 否 | 关键词搜索 |
| keyword_type | integer | 否 | 搜索类型(0=别名, 1=账号) |
| type_id | integer | 否 | 账号类型(0=账号密码, 1=SMS) |
成功响应 (200)
{
"code": 0,
"message": "success",
"data": {
"total": 100,
"hits": [
{
"key_id": "1234567890",
"account": "test@example.com",
"account_id": "",
"alias": "测试账号",
"description": "测试描述",
"status_id": 1,
"type_id": 0,
"is_empty_pwd": 0,
"update_time": 1700000000,
"apps": [
{
"team_application_id": "1",
"name": "应用名称",
"url": "https://app.com",
"group_status_id": 1,
"status_id": 1
}
]
}
]
}
}
错误响应
| HTTP码 | 错误码 | 错误内容 | 错误原因 |
|---|---|---|---|
| 400 | -2 | 无效参数输入 | 参数格式错误 |
| 401 | 1201007 | 缺少 Token | 缺少 Authorization 请求头 |
| 401 | 1201008 | 无效 Token | Token 格式错误或已过期 |
| 500 | -3 | 数据服务异常 | 数据库操作失败 |
POST /v1/vault/grant - 账号密码-应用授权
为指定的账号密码绑定应用进行成员或分组授权。
认证方式: Bearer Token
请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| key_id | string | 是 | 账号ID |
| team_application_id | string | 是 | 团队应用ID |
| group_status_id | integer | 是 | 分组状态ID(0=未授权, 1=指定分组/成员, 2=授权全部成员) |
| member_ids | array[string] | 否 | 成员ID数组(当 group_status_id=1 时有效) |
| group_ids | array[string] | 否 | 分组ID数组(当 group_status_id=1 时有效) |
权限说明
- team_id 从 Token 中自动提取
- key_id 必须属于当前团队(越权检查)
- team_application_id 必须属于当前团队(越权检查)
- member_ids、group_ids 会被过滤,只保留属于当前团队的合法 ID
成功响应 (200)
{
"code": 0,
"message": "success",
"data": null
}
错误响应
| HTTP码 | 错误码 | 错误内容 | 错误原因 |
|---|---|---|---|
| 400 | -2 | 无效参数输入 | 参数格式错误或 group_status_id 不在 0-2 范围内 |
| 401 | 1201007 | 缺少 Token | 缺少 Authorization 请求头 |
| 401 | 1201008 | 无效 Token | Token 格式错误或已过期 |
| 404 | -9 | 记录不存在 | key_id 或 team_application_id 不属于当前团队 |
| 500 | -3 | 数据服务异常 | 数据库操作失败 |
GET /v1/vault/categories - 获取账号分类列表
获取当前团队下的所有分类。
认证方式: Bearer Token
成功响应 (200)
{
"code": 0,
"message": "success",
"data": {
"node_list": [
{
"id": 1,
"name": "分类1"
},
{
"id": 2,
"name": "分类2"
}
]
}
}
错误响应
| HTTP码 | 错误码 | 错误内容 | 错误原因 |
|---|---|---|---|
| 400 | 1202005 | teamId 无效 | teamId 为 0 |
| 401 | 1201007 | 缺少 Token | 缺少 Authorization 请求头 |
| 401 | 1201008 | 无效 Token | Token 格式错误或已过期 |
| 403 | 1201009 | 无效 scope | scope 不为 "openapi" |
| 403 | 1201011 | teamId 为空 | Token 中 team id 为 0 |
| 500 | -3 | 数据服务异常 | 数据库查询失败 |
TeamApplication
GET /v1/team-applications/options - 获取团队应用列表
获取当前团队下的所有应用及其分类信息。
认证方式: Bearer Token
成功响应 (200)
{
"code": 0,
"data": {
"apps": [
{
"team_application_id": 1,
"name": "应用名称",
"category_ids": [1, 2]
}
],
"categories": [
{
"id": 1,
"name": "分类名称"
}
]
}
}
错误响应
| HTTP码 | 错误码 | 错误内容 | 错误原因 |
|---|---|---|---|
| 400 | 1202005 | teamId 无效 | teamId 为 0 |
| 401 | 1201007 | 缺少 Token | 缺少 Authorization 请求头 |
| 401 | 1201008 | 无效 Token | Token 格式错误或已过期 |
| 403 | 1201009 | 无效 scope | scope 不为 "openapi" |
| 403 | 1201011 | teamId 为空 | Token 中 team id 为 0 |
| 500 | -3 | 数据服务异常 | 数据库查询失败 |
Organization
GET /v1/team/department - 获取组织架构
获取部门/分组和成员的树形结构。
认证方式: Bearer Token
请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| type | integer | 否 | 组织类型筛选:0-全部,1-仅部门/分组,2-仅成员 |
成功响应 (200)
{
"code": 0,
"data": {
"hits": [
{
"id": "1",
"dept_id": "1",
"name": "部门名称",
"is_group": true,
"is_grant": true,
"member_count": 10,
"total_member_count": 100,
"children": []
}
]
}
}
OrgNode 节点属性
| 属性 | 类型 | 说明 |
|---|---|---|
| id | string | 节点ID |
| dept_id | string | 部门ID(仅部门节点有值) |
| name | string | 节点名称 |
| is_group | boolean | 是否为分组/部门(true为分组,false为成员) |
| is_grant | boolean | 是否有权限 |
| member_count | integer | 成员数量(仅分组节点有值) |
| total_member_count | integer | 总成员数量(仅分组节点有值) |
| children | array | 子节点 |
| member_type | integer | 成员类型(仅成员节点有值) |
错误响应
| HTTP码 | 错误码 | 错误内容 | 错误原因 |
|---|---|---|---|
| 400 | 1202005 | teamId 无效 | teamId 为 0 |
| 401 | 1201007 | 缺少 Token | 缺少 Authorization 请求头 |
| 401 | 1201008 | 无效 Token | Token 格式错误或已过期 |
| 403 | 1201009 | 无效 scope | scope 不为 "openapi" |
| 403 | 1201011 | teamId 为空 | Token 中 team id 为 0 |
| 500 | -3 | 数据服务异常 | 数据库查询失败 |
SMS
GET /v1/sms/list - 获取短信列表
获取当前团队的短信发送记录。
认证方式: Bearer Token
请求参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| page | integer | 否 | 1 | 页码 |
| page_size | integer | 否 | 20 | 每页数量 |
成功响应 (200)
{
"code": 0,
"data": {
"hits": [],
"total": 0
}
}
错误响应
| HTTP码 | 错误码 | 错误内容 | 错误原因 |
|---|---|---|---|
| 400 | 1202005 | teamId 无效 | teamId 为 0 |
| 401 | 1201007 | 缺少 Token | 缺少 Authorization 请求头 |
| 401 | 1201008 | 无效 Token | Token 格式错误或已过期 |
| 403 | 1201009 | 无效 scope | scope 不为 "openapi" |
| 403 | 1201011 | teamId 为空 | Token 中 team id 为 0 |
| 500 | -3 | 数据服务异常 | 数据库查询失败 |
数据结构
ErrorResponse
错误响应结构。
| 属性 | 类型 | 说明 |
|---|---|---|
| code | integer | 错误码 |
| message | string | 错误信息 |
| data | object | 错误详情(可选) |
更新日志
| 日期 | 版本 | 说明 |
|---|---|---|
| 2026-03-24 | 1.0.0 | 初始版本,包含 Token 生成和 Vault 接口 |
| 2026-03-27 | 1.0.1 | 更新API地址 |
文档内容是否对您有帮助?
如果遇到产品相关问题,您可咨询 在线客服 寻求帮助。





相关问题
其他问题