Skip to content

API 调用指南 ​

OpenAPI 提供标准的 HTTP 接口,用于管理云手机实例、任务及相关资源。

快速开始 ​

请在 控制台 -> 开发者 -> Open API 页面获取 API 凭证(Access Key ID / Secret Access Key),并在所有 API 请求中通过 X-Access-Key 请求头传递获取到的 Access Key ID。

API 规则 ​

请求头 ​

每个 API 请求都必须在 HTTP Header 中包含以下四个字段:

Header 字段必填说明
X-Access-Key是API 访问密钥 ID,用于标识调用方
X-Timestamp是毫秒级 Unix 时间戳(13 位),用于防止重放攻击
X-Nonce是随机字符串,10~32 位小写字母和数字,每次请求唯一
X-Signature是签名值,Base64 编码的 HMAC-SHA256 结果

WARNING

以上四个 Header 字段均为必填。缺失或无效的字段将导致 401 Unauthorized 响应。

回调 ​

所有支持回调的 API 都会将数据发送到用户提供的回调 URL。

回调 URL 可在 控制台 -> 开发者 -> Open API 中修改。

回调消息携带签名,接收方应按 回调签名算法 验证签名,以确保消息来源可靠且未被篡改。

签名算法 ​

签名采用 HMAC-SHA256 算法,计算过程如下:

步骤 1:构造签名字符串(SignString)

按固定顺序拼接以下四个部分,中间不加任何分隔符:

SignString = uriPath + timestamp + nonce + accessKey

步骤 2:转换为小写

SignString = SignString.toLowerCase()

步骤 3:HMAC-SHA256 计算

以 secretKey 为密钥,对转换为小写后的签名字符串进行 HMAC-SHA256 运算,得到字节数组。

步骤 4:Base64 编码

将 HMAC-SHA256 计算得到的字节数组进行 Base64 编码,得到最终的签名值。

签名公式 ​

X-Signature = Base64( HMAC-SHA256( SignString.toLowerCase(), secretKey ) )

参数说明 ​

参数类型取值范围说明
uriPathString例如 /api/v1/orders请求路径,不包含域名和查询参数
timestampString13 位整数毫秒级 Unix 时间戳
nonceString10~32 个字符小写字母 a-z 和数字 0-9 的随机组合
accessKeyString-API Access Key ID,与 X-Access-Key 一致
secretKeyString机密API Secret Access Key,仅用于签名计算,绝不传输

签名示例 ​

示例参数:

参数示例值
uriPath/api/v1/orders
timestamp1719800000000
noncea3f7b2c9d1e8
accessKeyak-7x9k2m4p
secretKeysk-5n8q3r6t1w9y2u4i7o0p

计算过程:

步骤 1:构造签名字符串
SignString = "/api/v1/orders" + "1719800000000" + "a3f7b2c9d1e8" + "ak-7x9k2m4p"
          = "/api/v1/orders1719800000000a3f7b2c9d1e8ak-7x9k2m4p"

步骤 2:转换为小写
SignString = "/api/v1/orders1719800000000a3f7b2c9d1e8ak-7x9k2m4p".toLowerCase()

步骤 3 & 4:HMAC-SHA256 + Base64
signature = Base64( HMAC-SHA256(SignString, secretKey) )

最终请求示例:

http
POST /api/v1/orders HTTP/1.1
Content-Type: application/json
X-Access-Key: ak-7x9k2m4p
X-Timestamp: 1719800000000
X-Nonce: a3f7b2c9d1e8
X-Signature: xZ9vK2mP3nQ8wR5tY7uI1oP4aS6dF3gH9jK2lM5nB8cX7vN1mQ4wE6rT9yU2iO3pL=

{"product_id": 123, "quantity": 2}

回调签名算法 ​

回调消息同样采用 HMAC-SHA256 签名。与请求签名的区别在于:签名字符串的四个字段之间以单个换行符 \n(ASCII 码 0x0A)分隔,拼接完成后需将整个字符串统一转换为全小写形式。

步骤 1:构造签名字符串(SignString)

按固定顺序拼接以下四个字段,字段之间以单个换行符 \n 分隔:

SignString = uriPath + "\n" + timestamp + "\n" + nonce + "\n" + accessKey

步骤 2:转换为小写

SignString = SignString.toLowerCase()

步骤 3:HMAC-SHA256 计算

以双方约定的 secretKey(对应 Web 端的 Secret Access Key,绝不传输)作为 HMAC 密钥,对转换为小写后的签名字符串执行 HmacSHA256 运算,得到 32 字节的二进制摘要。

步骤 4:Base64 编码

对摘要字节数组执行 Base64 编码,得到的字符串即为签名值。

签名公式:

sign = Base64( HmacSHA256( SecretKey, toLowerCase(uriPath + "\n" + timestamp + "\n" + nonce + "\n" + accessKey) ) )

TIP

回调签名与请求签名使用相同的参数和密钥,但拼接方式不同:请求签名四个部分直接拼接、中间不加任何分隔符;回调签名四个字段之间以单个换行符 \n 分隔。

代码示例 ​

Java ​

java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Random;

public class ApiSignatureUtil {
    private static final String HMAC_ALGORITHM = "HmacSHA256";
    private static final String CHARSET = "abcdefghijklmnopqrstuvwxyz0123456789";

    /**
     * 生成带签名的请求头
     */
    public static SignatureResult sign(String uriPath, String accessKey, String secretKey) {
        // 1. 生成 13 位毫秒级时间戳
        String timestamp = String.valueOf(System.currentTimeMillis());

        // 2. 生成 10~32 位随机 nonce
        String nonce = generateNonce(10, 32);

        // 3. 构造签名字符串并转换为小写
        String signString = uriPath + timestamp + nonce + accessKey;
        signString = signString.toLowerCase();

        // 4. HMAC-SHA256 + Base64 编码
        String signature = hmacSha256Base64(signString, secretKey);

        return new SignatureResult(accessKey, timestamp, nonce, signature);
    }

    private static String hmacSha256Base64(String data, String key) {
        try {
            Mac mac = Mac.getInstance(HMAC_ALGORITHM);
            SecretKeySpec keySpec = new SecretKeySpec(
                    key.getBytes(StandardCharsets.UTF_8), HMAC_ALGORITHM);
            mac.init(keySpec);
            byte[] rawBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(rawBytes);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            throw new RuntimeException("签名计算失败", e);
        }
    }

    private static String generateNonce(int minLen, int maxLen) {
        String chars = CHARSET;
        Random random = new Random();
        int len = random.nextInt(maxLen - minLen + 1) + minLen;
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            sb.append(chars.charAt(random.nextInt(chars.length())));
        }
        return sb.toString();
    }

    public static class SignatureResult {
        public final String accessKey;
        public final String timestamp;
        public final String nonce;
        public final String signature;

        public SignatureResult(String accessKey, String timestamp,
                String nonce, String signature) {
            this.accessKey = accessKey;
            this.timestamp = timestamp;
            this.nonce = nonce;
            this.signature = signature;
        }
    }
}

使用方式:

java
String uriPath = "/api/v1/orders";
String accessKey = "ak-7x9k2m4p";
String secretKey = "sk-5n8q3r6t1w9y2u4i7o0p";

ApiSignatureUtil.SignatureResult result =
        ApiSignatureUtil.sign(uriPath, accessKey, secretKey);

// 设置请求头
headers.put("X-Access-Key", result.accessKey);
headers.put("X-Timestamp", result.timestamp);
headers.put("X-Nonce", result.nonce);
headers.put("X-Signature", result.signature);

Python ​

python
import hmac
import hashlib
import base64
import time
import random
import string


def generate_nonce(min_len=10, max_len=32):
    """生成随机 nonce"""
    chars = string.ascii_lowercase + string.digits
    length = random.randint(min_len, max_len)
    return ''.join(random.choices(chars, k=length))


def sign_request(uri_path, access_key, secret_key):
    """生成签名和请求头参数"""
    timestamp = str(int(time.time() * 1000))
    nonce = generate_nonce()

    # 构造签名字符串并转换为小写
    sign_string = (uri_path + timestamp + nonce + access_key).lower()

    # HMAC-SHA256 + Base64
    raw_signature = hmac.new(
        secret_key.encode('utf-8'),
        sign_string.encode('utf-8'),
        hashlib.sha256
    ).digest()
    signature = base64.b64encode(raw_signature).decode('utf-8')

    return {
        'X-Access-Key': access_key,
        'X-Timestamp': timestamp,
        'X-Nonce': nonce,
        'X-Signature': signature
    }

最佳实践 ​

安全 ​

DANGER

secretKey 是签名计算的核心密钥,请像对待密码一样妥善保管,切勿:

  • 在前端代码(浏览器/App)中硬编码 secretKey
  • 将 secretKey 提交到公开代码仓库(如 GitHub、GitLab 公开项目)
  • 在日志或错误信息中打印 secretKey
  • 通过 HTTP 明文传输 secretKey

建议使用环境变量或密钥管理系统(如 Vault、KMS)存储 secretKey,避免在代码中以明文形式出现。

Nonce 唯一性 ​

X-Nonce 在其有效期内(通常与时间戳窗口对齐,建议 5 分钟)必须唯一。服务端会维护已使用的 nonce 列表进行去重,重复使用的 nonce 将被拒绝。

时间同步 ​

服务端允许的时间偏差窗口建议设置为 ±5 分钟。请确保调用方服务器保持 NTP 时间同步,避免因时间漂移导致签名失败。

URI 路径规范 ​

构造签名时,uriPath 必须遵循以下规则:

  • 仅包含路径部分,不包含域名、端口和查询参数
  • 保留原始大小写,然后将整个拼接后的字符串转换为小写
  • 以 / 开头,不以 / 结尾(除非 API 本身如此定义)
  • 不包含 URL 编码,使用原始路径字符
场景正确错误
完整 URL/api/v1/users/api/v1/users
带查询参数/api/v1/search/api/v1/search?q=test
大小写混合路径/API/V1/Users → 拼接后转小写直接以原始大小写拼接

OpenAPI Docs