VMOS Cloud API
  • 简体中文
  • English
  • 简体中文
  • English
  • 产品介绍
  • 产品类型
  • 产品计费
  • OpenAPI
    • 使用指南
    • V2 简化签名
    • 接口文档
    • LLMs.txt(AI快速参考)
    • OpenAPI 规范(AI 专用)
    • 错误码
    • 实例属性列表
    • 安卓改机属性列表
    • 回调任务业务类型码
    • 更新日志
  • Android端 SDK
    • 示例搭建
    • 接口说明
    • 回调函数
    • 错误码
    • 更新日志
  • Web H5端 SDK
    • 示例搭建
    • 接口说明
    • H5 SDK 回调函数
    • 错误码
    • 更新日志
  • Windows PC端 SDK
    • 示例搭建
    • 接口说明
    • 回调函数
    • 更新日志
  • 端侧与云机通信开发
    • AIDL接入方式
    • 系统服务API(aidl)
  • 类XP、LSP Hook框架
    • 类Xposed、LSPosed框架
    • 传感器数据动态仿真
  • 相关协议

概述

V2 是 VMOSCloud OpenAPI 的简化签名方案,与原 V4(HMAC-SHA256 派生密钥)方案并存,AK / SK 共用。

已经接入 V4 的客户无需迁移,老 SDK 长期保留。新对接客户建议直接使用 V2:3 个 Header + 1 行 SHA-256 即可完成签名,不需要派生密钥、不需要重排 JSON、不需要 UTC 时间格式化。

获取账号 (AK/SK)

前置准备

在开始使用 API 前,需要获取 Access Key ID 和 Secret Access Key,用于 API 请求鉴权。

操作步骤:

  1. 登录 VMOSCloud 平台
  2. 导航至菜单:开发者 -> API
  3. 复制对应的密钥信息:
    • AccessKeyID (即 AK)
    • SecretAccessKey (即 SK)

请求 Header

每次请求 Headers 中必须包含以下 3 个鉴权参数:

参数名类型必填参数描述示例值
X-Access-Keystring必须Access Key ID 明文ak_xxxxxxxxxxxx
X-Timestampstring必须unix 秒级时间戳(10 位整数字符串),±5 分钟窗1747555200
X-Signstring必须64 位小写 hex 签名6069dfb4cb3ac57b…
Content-TypestringPOST必须资源 MIME 类型application/json

签名算法

直接字符串拼接,无分隔符:

signString = SK + X-Timestamp + path + bodyOrQuery
X-Sign     = lowerHex( SHA-256( signString_UTF8 ) )
字段取值规则
SKSecret Access Key 明文
X-Timestamp与请求头一致的 10 位 unix 秒(毫秒级会被判过期)
path含 servlet 前缀的完整路径,例如 /vcpcloud/api/padApi/padInfo
bodyOrQuery见下表

bodyOrQuery 取值规则

请求类型bodyOrQuery
GET原始 query string,例如 startDate=2026-05-01&endDate=2026-05-31;无参则空串
POST / PUT(JSON)原始 body(发什么签什么,不要重排、不要去空格)
文件上传接口 /uploadFile / /asyncCmd / /syncCmd空串(文件 body 不参与签名)

query string 的参数顺序与 URL 编码必须与实际发送的完全一致,签名前不要重排、不要二次转码。

签名示例

输入:

SK   = 9cucpjoyn4xxmkhj3q9el3ce
ts   = 1747555200
path = /vcpcloud/api/padApi/padInfo
body = {"padCode":"AC32010601132"}

拼接后的 signString:

9cucpjoyn4xxmkhj3q9el3ce1747555200/vcpcloud/api/padApi/padInfo{"padCode":"AC32010601132"}

X-Sign = lowerHex( SHA-256( signString ) ),可以用任意语言的标准 SHA-256 库验证客户端实现是否正确。

错误码

code含义
2019验证签名失败
2031无效的密钥(AccessKey 找不到)
2032请求头缺必要参数
2033时间戳过期或格式错误

调用示例

curl

TS=$(date +%s)
AK="ak_xxxxxxxxxxxx"
SK="sk_xxxxxxxxxxxx"
URI="/vcpcloud/api/padApi/padInfo"
BODY='{"padCode":"AC32010601132"}'

SIGN=$(printf "%s" "${SK}${TS}${URI}${BODY}" | openssl dgst -sha256 -hex | awk '{print $2}')

curl -X POST "https://api.vmoscloud.com${URI}" \
  -H "X-Access-Key: ${AK}" \
  -H "X-Timestamp: ${TS}" \
  -H "X-Sign: ${SIGN}" \
  -H "Content-Type: application/json" \
  --data-binary "${BODY}"

java

import com.alibaba.fastjson.JSONObject;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Map;

public class V2ApiRequestUtils {

    private static final String BASE_URL = "https://api.vmoscloud.com";
    private static final String ACCESS_KEY = "Access Key ID";
    private static final String SECRET_ACCESS_KEY = "Secret Access Key";

    public static String sendPostRequest(String path, JSONObject params) {
        try {
            String body = params == null ? "" : params.toJSONString();
            String ts = String.valueOf(System.currentTimeMillis() / 1000);

            HttpPost req = new HttpPost(BASE_URL + path);
            req.setHeader("X-Access-Key", ACCESS_KEY);
            req.setHeader("X-Timestamp", ts);
            req.setHeader("X-Sign", sign(ts, path, body));
            req.setHeader("Content-Type", "application/json");
            req.setEntity(new StringEntity(body, StandardCharsets.UTF_8));

            try (CloseableHttpClient client = HttpClients.createDefault();
                 CloseableHttpResponse resp = client.execute(req)) {
                return EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8);
            }
        } catch (Exception e) {
            throw new RuntimeException("V2 POST failed: " + path, e);
        }
    }

    public static String sendGetRequest(String path, JSONObject params) {
        try {
            String query = buildQuery(params);
            String ts = String.valueOf(System.currentTimeMillis() / 1000);

            HttpGet req = new HttpGet(BASE_URL + path + (query.isEmpty() ? "" : "?" + query));
            req.setHeader("X-Access-Key", ACCESS_KEY);
            req.setHeader("X-Timestamp", ts);
            req.setHeader("X-Sign", sign(ts, path, query));

            try (CloseableHttpClient client = HttpClients.createDefault();
                 CloseableHttpResponse resp = client.execute(req)) {
                return EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8);
            }
        } catch (Exception e) {
            throw new RuntimeException("V2 GET failed: " + path, e);
        }
    }

    private static String sign(String ts, String path, String body) throws Exception {
        String signString = SECRET_ACCESS_KEY + ts + path + body;
        byte[] raw = MessageDigest.getInstance("SHA-256")
                .digest(signString.getBytes(StandardCharsets.UTF_8));
        return String.format("%064x", new BigInteger(1, raw));
    }

    private static String buildQuery(JSONObject params) {
        if (params == null || params.isEmpty()) return "";
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Object> e : params.entrySet()) {
            if (sb.length() > 0) sb.append('&');
            sb.append(e.getKey()).append('=').append(e.getValue());
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        // POST 示例
        JSONObject body = new JSONObject();
        body.put("padCode", "AC32010601132");
        System.out.println(sendPostRequest("/vcpcloud/api/padApi/padInfo", body));

        // GET 示例
        JSONObject query = new JSONObject();
        query.put("startDate", "2026-05-01");
        query.put("endDate", "2026-05-31");
        System.out.println(sendGetRequest("/vcpcloud/api/padApi/getOrderEquipmentList", query));
    }
}

python

import hashlib
import time
import json
import requests

BASE_URL = "https://api.vmoscloud.com"
ACCESS_KEY = "Access Key ID"
SECRET_ACCESS_KEY = "Secret Access Key"


def _sign(ts: str, path: str, body_or_query: str) -> str:
    return hashlib.sha256(
        (SECRET_ACCESS_KEY + ts + path + body_or_query).encode("utf-8")
    ).hexdigest()


def send_post(path: str, params: dict | None = None) -> str:
    body = json.dumps(params, separators=(",", ":"), ensure_ascii=False) if params else ""
    ts = str(int(time.time()))
    headers = {
        "X-Access-Key": ACCESS_KEY,
        "X-Timestamp": ts,
        "X-Sign": _sign(ts, path, body),
        "Content-Type": "application/json",
    }
    # 用 data=bytes 避免 requests 改写 body
    resp = requests.post(BASE_URL + path, headers=headers, data=body.encode("utf-8"))
    return resp.text


def send_get(path: str, params: dict | None = None) -> str:
    query = "&".join(f"{k}={v}" for k, v in params.items()) if params else ""
    ts = str(int(time.time()))
    url = BASE_URL + path + (f"?{query}" if query else "")
    headers = {
        "X-Access-Key": ACCESS_KEY,
        "X-Timestamp": ts,
        "X-Sign": _sign(ts, path, query),
    }
    return requests.get(url, headers=headers).text


if __name__ == "__main__":
    # POST
    print(send_post("/vcpcloud/api/padApi/padInfo", {"padCode": "AC32010601132"}))
    # GET
    print(send_get("/vcpcloud/api/padApi/getOrderEquipmentList",
                   {"startDate": "2026-05-01", "endDate": "2026-05-31"}))

node

const crypto = require("crypto");

const BASE_URL = "https://api.vmoscloud.com";
const ACCESS_KEY = "Access Key ID";
const SECRET_ACCESS_KEY = "Secret Access Key";

function sign(ts, path, bodyOrQuery) {
    return crypto
        .createHash("sha256")
        .update(SECRET_ACCESS_KEY + ts + path + bodyOrQuery, "utf8")
        .digest("hex");
}

async function sendPost(path, params) {
    const body = params ? JSON.stringify(params) : "";
    const ts = Math.floor(Date.now() / 1000).toString();
    const resp = await fetch(BASE_URL + path, {
        method: "POST",
        headers: {
            "X-Access-Key": ACCESS_KEY,
            "X-Timestamp": ts,
            "X-Sign": sign(ts, path, body),
            "Content-Type": "application/json",
        },
        body,
    });
    return resp.text();
}

async function sendGet(path, params) {
    const query = params
        ? Object.entries(params).map(([k, v]) => `${k}=${v}`).join("&")
        : "";
    const ts = Math.floor(Date.now() / 1000).toString();
    const resp = await fetch(BASE_URL + path + (query ? `?${query}` : ""), {
        headers: {
            "X-Access-Key": ACCESS_KEY,
            "X-Timestamp": ts,
            "X-Sign": sign(ts, path, query),
        },
    });
    return resp.text();
}

(async () => {
    // POST
    console.log(await sendPost("/vcpcloud/api/padApi/padInfo",
        { padCode: "AC32010601132" }));
    // GET
    console.log(await sendGet("/vcpcloud/api/padApi/getOrderEquipmentList",
        { startDate: "2026-05-01", endDate: "2026-05-31" }));
})();

go

package main

import (
    "bytes"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "strconv"
    "strings"
    "time"
)

const (
    baseURL         = "https://api.vmoscloud.com"
    accessKey       = "Access Key ID"
    secretAccessKey = "Secret Access Key"
)

func sign(ts, path, bodyOrQuery string) string {
    sum := sha256.Sum256([]byte(secretAccessKey + ts + path + bodyOrQuery))
    return hex.EncodeToString(sum[:])
}

func sendPost(path string, params map[string]interface{}) (string, error) {
    body := ""
    if params != nil {
        b, _ := json.Marshal(params)
        body = string(b)
    }
    ts := strconv.FormatInt(time.Now().Unix(), 10)

    req, _ := http.NewRequest("POST", baseURL+path, bytes.NewReader([]byte(body)))
    req.Header.Set("X-Access-Key", accessKey)
    req.Header.Set("X-Timestamp", ts)
    req.Header.Set("X-Sign", sign(ts, path, body))
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()
    bs, _ := io.ReadAll(resp.Body)
    return string(bs), nil
}

func sendGet(path string, params map[string]string) (string, error) {
    parts := make([]string, 0, len(params))
    for k, v := range params {
        parts = append(parts, k+"="+v)
    }
    query := strings.Join(parts, "&")
    ts := strconv.FormatInt(time.Now().Unix(), 10)

    url := baseURL + path
    if query != "" {
        url += "?" + query
    }
    req, _ := http.NewRequest("GET", url, nil)
    req.Header.Set("X-Access-Key", accessKey)
    req.Header.Set("X-Timestamp", ts)
    req.Header.Set("X-Sign", sign(ts, path, query))

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()
    bs, _ := io.ReadAll(resp.Body)
    return string(bs), nil
}

func main() {
    // POST
    out, _ := sendPost("/vcpcloud/api/padApi/padInfo",
        map[string]interface{}{"padCode": "AC32010601132"})
    fmt.Println(out)

    // GET
    out, _ = sendGet("/vcpcloud/api/padApi/getOrderEquipmentList",
        map[string]string{"startDate": "2026-05-01", "endDate": "2026-05-31"})
    fmt.Println(out)
}

php

<?php

const BASE_URL = "https://api.vmoscloud.com";
const ACCESS_KEY = "Access Key ID";
const SECRET_ACCESS_KEY = "Secret Access Key";

function v2_sign(string $ts, string $path, string $bodyOrQuery): string {
    return hash("sha256", SECRET_ACCESS_KEY . $ts . $path . $bodyOrQuery);
}

function send_post(string $path, ?array $params = null): string {
    $body = $params === null ? "" : json_encode($params, JSON_UNESCAPED_UNICODE);
    $ts = (string) time();

    $ch = curl_init(BASE_URL . $path);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $body,
        CURLOPT_HTTPHEADER     => [
            "X-Access-Key: " . ACCESS_KEY,
            "X-Timestamp: " . $ts,
            "X-Sign: " . v2_sign($ts, $path, $body),
            "Content-Type: application/json",
        ],
    ]);
    $resp = curl_exec($ch);
    curl_close($ch);
    return $resp;
}

function send_get(string $path, ?array $params = null): string {
    $query = $params ? http_build_query($params) : "";
    $ts = (string) time();
    $url = BASE_URL . $path . ($query ? "?$query" : "");

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            "X-Access-Key: " . ACCESS_KEY,
            "X-Timestamp: " . $ts,
            "X-Sign: " . v2_sign($ts, $path, $query),
        ],
    ]);
    $resp = curl_exec($ch);
    curl_close($ch);
    return $resp;
}

// POST
echo send_post("/vcpcloud/api/padApi/padInfo", ["padCode" => "AC32010601132"]) . PHP_EOL;
// GET
echo send_get("/vcpcloud/api/padApi/getOrderEquipmentList",
    ["startDate" => "2026-05-01", "endDate" => "2026-05-31"]) . PHP_EOL;

.net

using System;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;

public static class V2ApiRequestUtils
{
    private const string BaseUrl = "https://api.vmoscloud.com";
    private const string AccessKey = "Access Key ID";
    private const string SecretAccessKey = "Secret Access Key";

    private static readonly HttpClient Http = new HttpClient();

    private static string Sign(string ts, string path, string bodyOrQuery)
    {
        using var sha = SHA256.Create();
        byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(SecretAccessKey + ts + path + bodyOrQuery));
        var sb = new StringBuilder(64);
        foreach (var b in hash) sb.Append(b.ToString("x2"));
        return sb.ToString();
    }

    public static async Task<string> SendPostAsync(string path, object? body)
    {
        string bodyJson = body == null ? "" : JsonSerializer.Serialize(body);
        string ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();

        var req = new HttpRequestMessage(HttpMethod.Post, BaseUrl + path)
        {
            Content = new StringContent(bodyJson, Encoding.UTF8, "application/json")
        };
        req.Headers.TryAddWithoutValidation("X-Access-Key", AccessKey);
        req.Headers.TryAddWithoutValidation("X-Timestamp", ts);
        req.Headers.TryAddWithoutValidation("X-Sign", Sign(ts, path, bodyJson));

        var resp = await Http.SendAsync(req);
        return await resp.Content.ReadAsStringAsync();
    }

    public static async Task<string> SendGetAsync(string path, Dictionary<string, string>? query)
    {
        string queryStr = query == null ? ""
            : string.Join("&", query.Select(kv => $"{kv.Key}={kv.Value}"));
        string ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();

        var url = BaseUrl + path + (queryStr.Length > 0 ? "?" + queryStr : "");
        var req = new HttpRequestMessage(HttpMethod.Get, url);
        req.Headers.TryAddWithoutValidation("X-Access-Key", AccessKey);
        req.Headers.TryAddWithoutValidation("X-Timestamp", ts);
        req.Headers.TryAddWithoutValidation("X-Sign", Sign(ts, path, queryStr));

        var resp = await Http.SendAsync(req);
        return await resp.Content.ReadAsStringAsync();
    }

    public static async Task Main()
    {
        // POST
        Console.WriteLine(await SendPostAsync("/vcpcloud/api/padApi/padInfo",
            new { padCode = "AC32010601132" }));
        // GET
        Console.WriteLine(await SendGetAsync("/vcpcloud/api/padApi/getOrderEquipmentList",
            new Dictionary<string, string> { ["startDate"] = "2026-05-01", ["endDate"] = "2026-05-31" }));
    }
}

常见问题

Q1:老客户是否需要迁移到 V2? 不需要。V4 长期保留,老 SDK 继续用即可。详见 V4 签名机制。

Q2:同一对 AK/SK 是否两套方案都能用? 可以。AK/SK 共用,按请求选协议。

Q3:POST body 为空时 bodyOrQuery 填什么? 空字符串。signString = SK + ts + path,末尾不再拼东西。

Q4:multipart 文件上传接口怎么签?bodyOrQuery 留空串,文件 body 不参与签名。涉及接口:/uploadFile、/asyncCmd、/syncCmd。

Q5:时间戳能用毫秒级吗? 不行。必须 10 位 unix 秒。13 位毫秒会因偏离当前时间超 5 分钟直接返回 2033。

Q6:X-Sign 大小写敏感吗? 服务端忽略大小写比较,大小写 hex 都接受。建议客户端统一输出小写。

Q7:如何分流到 V2 / V4? 带 X-Sign 头走 V2;带 Authorization 头走 V4。两套同时启用,按 Header 自动识别。

Prev
使用指南
Next
接口文档