Appearance
认证与签名
所有接口均需通过 API Key + 请求签名 进行认证。
请求头说明
| 请求头 | 必填 | 说明 |
|---|---|---|
Authorization | ✅ | 格式:Bearer <apiKey> |
X-Timestamp | ✅ | Unix 时间戳(秒),与服务器时差不超过 5 分钟 |
X-Signature | ✅ | 请求签名,见下方签名算法 |
X-Request-ID | ✅ | 请求唯一标识,建议使用 32 位随机字符串 |
X-User-ID | ✅ | 当前用户标识 |
Content-Type | ✅ | application/json(文件上传接口使用 multipart/form-data,无需手动设置) |
签名算法
签名用于防止请求篡改和重放攻击,算法为 HMAC-SHA256。
步骤:
- 构建规范化查询字符串(
canonicalQuery):取 URL 查询参数中所有字段(排除null、空字符串),按字段名**字典序(ASCII 升序)**排序,拼接为key1=value1&key2=value2&...格式;无查询参数时为空字符串 - 构建规范化请求体字符串(
canonicalBody):取 JSON 请求体中所有字段(排除null、空字符串、纯空白值),按字段名**字典序(ASCII 升序)**排序,拼接为key1=value1&key2=value2&...格式;无请求体或multipart/form-data接口时为空字符串 - 拼接签名基串(
signatureBase),各部分以换行符\n分隔:METHOD\nPATH\nX-Timestamp\nX-User-ID\ncanonicalQuery\ncanonicalBody - 以
apiSecret为密钥,对签名基串做 HMAC-SHA256,取小写十六进制结果作为X-Signature
示例(POST /v1/chat/stream):
METHOD = POST
PATH = /v1/chat/stream
X-Timestamp = 1742000000
X-User-ID = user-123
canonicalQuery = (空,无 URL 查询参数)
canonicalBody = agentId=agent-uuid&conversationId=conv-uuid&text=你好
signatureBase = "POST\n/v1/chat/stream\n1742000000\nuser-123\n\nagentId=agent-uuid&conversationId=conv-uuid&text=你好"
X-Signature = HMAC-SHA256(signatureBase, apiSecret)注意事项
- 字符串值去掉首尾空白后再参与签名
- 非字符串类型(如对象、数组)取其 JSON 字符串参与签名
- 对象/数组即使为空(如
{}/[])也会参与签名;若不希望参与,请省略该字段或传null multipart/form-data上传接口的canonicalBody固定为空字符串
JavaScript 签名工具函数
javascript
/**
* 构建认证请求头
* @param {string} apiKey - API Key
* @param {string} apiSecret - API Secret
* @param {string} userId - 用户 ID
* @param {Object} body - 请求体对象(multipart 接口传 {})
* @param {Object} options - 配置项
* @param {string} options.url - 请求 URL(必填,用于提取 path 和 query)
* @param {string} [options.method='POST'] - HTTP 方法
* @param {boolean} [options.isStreamRequest=false] - 是否 SSE 流式请求
* @param {boolean} [options.isMultipart=false] - 是否 multipart/form-data
*/
async function buildAuthHeaders(apiKey, apiSecret, userId, body = {}, options = {}) {
const method = (options.method || 'POST').toUpperCase()
const timestamp = Math.floor(Date.now() / 1000).toString()
const requestId = generateSecureRandomString(32)
// 解析 URL,提取 path 和 query 参数
const resolvedUrl = new URL(options.url, window.location.origin)
const path = resolvedUrl.pathname
const queryParams = {}
resolvedUrl.searchParams.forEach((value, key) => {
queryParams[key] = value
})
// 规范化参数(排除 null 和空字符串,字典序排序)
function canonicalize(params) {
return Object.keys(params)
.filter(k => {
const v = params[k]
if (v == null) return false
if (typeof v === 'string') return v.trim() !== ''
return true
})
.sort()
.map(k => {
const v = params[k]
const val = typeof v === 'string' ? v.trim() : JSON.stringify(v)
return `${k}=${val}`
})
.join('&')
}
const canonicalQuery = canonicalize(queryParams)
const canonicalBody = options.isMultipart ? '' : canonicalize(body)
// 构建签名基串
const signatureBase = [method, path, timestamp, userId, canonicalQuery, canonicalBody].join('\n')
// HMAC-SHA256 签名
const encoder = new TextEncoder()
const cryptoKey = await crypto.subtle.importKey(
'raw',
encoder.encode(apiSecret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
)
const sigBuffer = await crypto.subtle.sign('HMAC', cryptoKey, encoder.encode(signatureBase))
const signature = Array.from(new Uint8Array(sigBuffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('')
const headers = {
'Authorization': `Bearer ${apiKey}`,
'X-User-ID': userId,
'X-Timestamp': timestamp,
'X-Signature': signature,
'X-Request-ID': requestId,
'Accept': options.isStreamRequest ? 'text/event-stream' : 'application/json',
}
if (!options.isMultipart) {
headers['Content-Type'] = 'application/json'
}
return headers
}
// 生成安全随机字符串
function generateSecureRandomString(length) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
const randomValues = new Uint8Array(length)
crypto.getRandomValues(randomValues)
return Array.from(randomValues, v => chars[v % chars.length]).join('')
}调用示例
javascript
// 普通 JSON 接口
const headers = await buildAuthHeaders(apiKey, apiSecret, userId, { agentId }, {
url: '/v1/chat/conversation',
})
// SSE 流式接口
const headers = await buildAuthHeaders(apiKey, apiSecret, userId, { agentId, conversationId, text }, {
url: '/v1/chat/stream',
isStreamRequest: true,
})
// multipart/form-data 文件上传接口
const headers = await buildAuthHeaders(apiKey, apiSecret, userId, {}, {
url: '/v1/agent/face-detect',
isMultipart: true,
})限流规则
每个 API Key 独立计算:
- 频率限制(Rate Limit):单位时间内最大请求次数,超出返回
Too many requests错误 - 总量限制(Max Usage):累计调用总次数上限,超出返回
API key usage limit reached错误
具体限制值由平台管理员在创建 API Key 时配置。
常见认证错误
| HTTP 状态码 | 原因 |
|---|---|
401 | 缺少 Authorization / X-Timestamp / X-Signature 等必要请求头 |
401 | 时间戳与服务器时差超过 5 分钟(防重放) |
401 | API Key 无效或已禁用 |
401 | 签名验证失败(参数顺序、路径提取或 secret 错误) |