ARMCLOUD Documentation Center
  • 简体中文
  • English
  • 简体中文
  • English
  • Product introduction
  • Product billing
  • Server OpenAPI
    • Usage Guide
    • API Signature v1.0
    • API Reference
    • Error Codes
    • Callback Business Type Codes
    • Instance List
    • Android Instance Property List
    • Changelog
  • Android SDK
    • Example Setup
    • Interface Description
    • Callback Function
    • Error Code
    • Changelog
  • Web H5 SDK
    • Example Setup
    • Interface Description
    • Callback Function
    • Error Code
    • Changelog
  • Windows PC SDK
    • Example Setup
    • Interface Description
    • Callback Function
    • Changelog
  • Edge-Cloud Communication Development
    • AIDL Access Method
    • System Service API (AIIDL)
  • Scenario solutions
    • XP-like LSP Hook framework
    • Dynamic simulation of sensor data
  • ADI Collection Tool
  • FAQ
    • Platform Basics
    • Storage
    • Network Connectivity
    • Application Management
    • Development and Integration
    • Performance Optimization
    • Billing
    • Technical Support
  • Related agreements
    • Cloud Phone SDK Privacy Policy

Overview

ArmCloud's server-side OpenAPI provides developers with a set of RESTful APIs for managing and operating cloud mobile service resources. Through these APIs, you can create, manage, and monitor cloud phone instances, enabling better integration and expansion of your applications.

To ensure API security, ArmCloud offers two versions of signature verification solutions:

  • V2.0 version⭐: Simplified signature scheme, recommended
  • V1.0 version: The traditional complete signature scheme, see [Interface Signature v1.0] (./UsageGuide.md) for details

version recommendations

Recommended to use version 2.0, which offers simpler request header setup and signature computation processes while maintaining high security.

Obtaining Account (AK/SK)

Before starting to use the API, you need to obtain the Access Key ID and Secret Access Key for API request authentication.

Operation Steps:

  1. Log in to the ARMCloud platform
  2. Go to the User Management - > Account Information menu
  3. AppKey is AK, appSecret is SK

V2.0 Signature Verification (Recommended ⭐).

API domain name

Important reminder

Please select the correct API domain name according to your service region

RegionDomain nameScope of application
Domestichttps://api.xiaosuanyun.comUsers in Chinese mainland
Overseashttps://openapi-hk.armcloud.netUsers overseas as well as in Hong Kong, Macau, and Taiwan

Request header settings

Version 2.0 requires adding the following four custom request headers to HTTP requests:

Request the head nameExplanationExample
authverAuthentication version, fixed value "2.0"2.0
x-akAccess key IDs are assigned by the ArmCloud platformLTAI4FzK8888888888888
x-timestampRequest initiation timestamp (milliseconds)1618900299000
x-signRequest signing results5d9f846a5254865894069d9a864a96696115f0edcac66de5f3a3515f58c44200

Signature Calculation Process

1. Construct the string to be signed

Core formula

String to be signed = timestamp + path + body

The content of body varies depending on the request method:

  • GET request: body = query parameter string (if there are query parameters)
  • POST request: body = JSON requests a body string
Detailed explanation

Parameter Composition:

ParametersExplanationExample
timestampA 13-bit millisecond timestamp, the same as the x-timestamp request header1618900299000
pathA complete API path must start with //openapi/open/device/list
bodyDifferent content depending on the type of request (see explanation below)See specific examples

Body Content Rules:

  1. GET Requests:

    • If query parameters are present: body = query parameter string
    • If no query parameters: body = "" (empty string)
  2. POST Requests:

    • body = compact JSON request body string
    • JSON must be a compact format without spaces or line breaks
Build examples

GET Request Example:

Request URL: https://api.xiaosuanyun.com/openapi/open/user/info?id=12345&type=basic
timestamp: 1618900299000
path: /openapi/open/user/info
body: id=12345&type=basic

String to sign: 1618900299000/openapi/open/user/infoid=12345&type=basic

POST Request Example:

Request URL: https://api.xiaosuanyun.com/openapi/open/device/list
Request body: {"page": 1, "rows": 10}
timestamp: 1618900400000
path: /openapi/open/device/list
body: {"page":1,"rows":10}

String to sign: 1618900400000/openapi/open/device/list{"page":1,"rows":10}

2. Calculate signatures

Using the HMAC-SHA256 algorithm and SecretKey as the key, perform signature calculations on the string to be signed in step 1:

**signature = HMAC-SHA256 (string to sign, SecretKey) **

is important

The calculated signature value must be represented as a lowercase hexadecimal string with a length of 64 characters.

Calculation Example
String to sign: 1618900400000/openapi/open/device/list{"page":1,"rows":10}
SecretKey: your_secret_key

signature = hmac_sha256(string_to_sign, SecretKey).toLowerCase()
Results: 3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1

Error response

When signature validation fails, the API gateway returns the following error:

{
  "code": 100005,
  "msg": "验证签名失败",
  "data": null
}

Common Errors:

  • AccessKey does not exist or is disabled
  • The request timestamp differs from the server time by more than 5 minutes
  • Signature calculation error
  • Incorrect request header formatting

Interface Rate Limiting Explanation

To ensure the stability and reliability of platform services, ArmCloud has implemented access frequency limits for all OpenAPI interfaces.

Flow Limiting Mechanism

Purpose of traffic restriction

The main purpose of interface rate limiting is to make the platform used by customers more stable and reliable, ensuring that all users can fairly enjoy service resources.

Flow Limiting Strategy:

Rate limiting calculation method

Rate limiting is calculated using a fixed time window, with each Access Key independently calculating limits. It meets both QPS and minute-per-minute constraints.

User typeQPS limit (per second)RPM limit (per minute)Explanation
Test Users200 times per second5,000 times per minuteFree users are restricted by default
Paying Users2,000 times per second30,000 beats per minuteRestrictions after paid upgrades

Explanation of Rate Limiting Dimensions:

  1. QPS Limit: The maximum number of requests within any 1-second time window
  2. Per Minute Limit: The maximum number of requests within any 1-minute time window
  3. Access Key Level: Each Access Key is calculated independently; the quota is not shared between different Keys
  4. All Interfaces Shared: Under a single Access Key, all API calls share the same rate limit

Rate Limit Response

When rate limiting is triggered, the API returns an HTTP status 429 Too Many Requests, with the following response:

{
  "msg": "Too many requests. Please try again later..",
  "code": 429,
  "data": null
}

Response Head Description:

Response headExplanationExample
X-RateLimit-LimitThe request limit for the current rate limiting cycle depends on the type of throttling200
X-RateLimit-RemainingThe number of available requests remaining in the current rate limiting period150
X-RateLimit-ResetCurrent rate limiting cycle reset timestamp (Unix timestamp)1618900361
X-RateLimit-TypeThe type of rate limit triggeredQPS/RPM

Best Practices

suggestion

  1. Reasonable Control of Request Frequency: It is recommended to implement request frequency control in the application to avoid sending large numbers of requests in a short period
  2. Implement Retry Mechanism: When receiving a 429 error, wait for an appropriate time before retrying; it is recommended to use the exponential avoidance algorithm
  3. Monitoring Response Heads: Focus on X-RateLimit-* response heads and actively control the rhythm of requests
  4. Batch Operation Optimization: Try to use interfaces that support batch operations to reduce the number of requests per transaction

Retry Strategy Example:

import time
import random

def api_request_with_retry(api_func, max_retries=3):
    """API request example with retry"""
    for attempt in range(max_retries + 1):
        try:
            response = api_func()
            if response.status_code == 429:
                if attempt < max_retries:
                    # Exponential retraction + random jitter
                    delay = (2 ** attempt) + random.uniform(0, 1)
                    time.sleep(delay)
                    continue
                else:
                    raise Exception("Request frequency limit, maximum retry count has been reached")
            return response
        except Exception as e:
            if attempt == max_retries:
                raise e
            time.sleep(1)

Notes

::: warning: Important reminder

  1. Please ensure the system time is accurate; the server allows a maximum time deviation of 5 minutes
  2. SecretKey is sensitive information. Do not store or transmit it in plaintext. It is recommended to use environment variables for
  3. Each request must be re-signed and cannot be reused
  4. Signature calculations must include the complete request path, including the front slash
  5. JSON must be in a compact format and cannot contain extra spaces or line breaks
  6. All strings must use UTF-8 encoding
  7. Please follow the interface rate limiting rules to avoid service interruptions caused by frequent calls :::

Client Demo (Java/Python/NodeJs)

Important Notes

API domain name selection

Choose the domain name corresponding to your service region:

  • Domestic users: https://api.xiaosuanyun.com
  • Overseas users: https://openapi-hk.armcloud.net

interface path description

The interface paths used in the following code examples (such as /openapi/open/device/list, /openapi/open/pad/net/storage/res/create) are for demonstration purposes only.

** for actual use, please refer to the OpenAPI Interface Documentation to obtain the exact interface path and parameters. **

Java implementation example

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Arrays;
import com.fasterxml.jackson.databind.ObjectMapper;

public class ArmCloudSignatureV2 {
    private static final String ALGORITHM = "HmacSHA256";
    private final String accessKeyId;
    private final String secretKey;
    private final ObjectMapper objectMapper = new ObjectMapper();

    public ArmCloudSignatureV2(String accessKeyId, String secretKey) {
        this.accessKeyId = accessKeyId;
        this.secretKey = secretKey;
    }

    // GET request signature
    public Map<String, String> signGetRequest(String path, String queryString) throws Exception {
        String timestamp = String.valueOf(Instant.now().toEpochMilli());
        String body = queryString != null ? queryString : "";
        String signature = calculateSignature(timestamp, path, body);

        Map<String, String> headers = new HashMap<>();
        headers.put("authver", "2.0");
        headers.put("x-ak", accessKeyId);
        headers.put("x-timestamp", timestamp);
        headers.put("x-sign", signature);
        return headers;
    }

    // POST request signature
    public Map<String, String> signPostRequest(String path, Object requestData) throws Exception {
        String timestamp = String.valueOf(Instant.now().toEpochMilli());
        String requestBody = objectMapper.writeValueAsString(requestData);
        String signature = calculateSignature(timestamp, path, requestBody);

        Map<String, String> headers = new HashMap<>();
        headers.put("authver", "2.0");
        headers.put("x-ak", accessKeyId);
        headers.put("x-timestamp", timestamp);
        headers.put("x-sign", signature);
        headers.put("Content-Type", "application/json");
        return headers;
    }

    private String calculateSignature(String timestamp, String path, String body) throws Exception {
        String stringToSign = timestamp + path + (body != null ? body : "");
        Mac hmacSha256 = Mac.getInstance(ALGORITHM);
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), ALGORITHM);
        hmacSha256.init(secretKeySpec);
        byte[] hash = hmacSha256.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
        return bytesToHex(hash);
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }
        return hexString.toString();
    }

    // Use examples
    public static void main(String[] args) throws Exception {
        ArmCloudSignatureV2 signer = new ArmCloudSignatureV2("LTAI4FzK8888888888888", "your_secret_key");

        // POST Request Example - Board List Query (For interface paths, please refer to the OpenAPI documentation)
        Map<String, Object> deviceListData = new HashMap<>();
        deviceListData.put("page", 1);
        deviceListData.put("rows", 10);
        deviceListData.put("padCodes", Arrays.asList("AC21020010391"));
        deviceListData.put("vmStatus", "1");
        deviceListData.put("deviceStatus", "0");

        // Note: The path shown here is for example only. Please refer to the OpenAPI documentation for the actual path
        Map<String, String> deviceListHeaders = signer.signPostRequest("/openapi/open/device/list", deviceListData);
        System.out.println("Board list query request header: " + deviceListHeaders);

        // POST Request Example - Creating a Cloud Instance (For interface paths and parameters, please refer to the OpenAPI documentation)
        Map<String, Object> createInstanceData = new HashMap<>();
        createInstanceData.put("clusterCode", "001");
        createInstanceData.put("specificationCode", "m2-3");
        createInstanceData.put("imageId", "img-25021828327");
        createInstanceData.put("screenLayoutCode", "realdevice_1440x3120x560");
        createInstanceData.put("number", 2);
        createInstanceData.put("dns", "8.8.8.8");
        createInstanceData.put("storageSize", 16);
        createInstanceData.put("realPhoneTemplateId", 36);

        // Note: The path shown here is for example only. Please refer to the OpenAPI documentation for the actual path
        Map<String, String> createHeaders = signer.signPostRequest("/openapi/open/pad/net/storage/res/create", createInstanceData);
        System.out.println("Create a cloud instance request header: " + createHeaders);
    }
}

Python implementation example

import hmac
import hashlib
import time
import json
import requests
from typing import Dict, Any, Optional

class ArmCloudSignatureV2:
    def __init__(self, access_key_id: str, secret_key: str, base_url: str = "https://api.xiaosuanyun.com"):
        self.access_key_id = access_key_id
        self.secret_key = secret_key
        self.base_url = base_url  # Mainland China: https://api.xiaosuanyun.com; overseas: https://openapi-hk.armcloud.net

    def _calculate_signature(self, timestamp: str, path: str, body: str = "") -> str:
        """Calculate HMAC-SHA256 signature"""
        string_to_sign = timestamp + path + (body if body else "")
        signature = hmac.new(
            self.secret_key.encode('utf-8'),
            string_to_sign.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()
        return signature

    def sign_get_request(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, str]:
        """Generates a signature header for the GET request"""
        timestamp = str(int(time.time() * 1000))
        query_string = ""

        if params:
            query_string = "&".join([f"{k}={v}" for k, v in params.items()])
            body = query_string
        else:
            body = ""

        signature = self._calculate_signature(timestamp, path, body)

        return {
            "authver": "2.0",
            "x-ak": self.access_key_id,
            "x-timestamp": timestamp,
            "x-sign": signature
        }

    def sign_post_request(self, path: str, data: Dict[str, Any]) -> Dict[str, str]:
        """Generates signature headers for POST requests"""
        timestamp = str(int(time.time() * 1000))
        request_body = json.dumps(data, ensure_ascii=False, separators=(',', ':'))
        signature = self._calculate_signature(timestamp, path, request_body)

        return {
            "authver": "2.0",
            "x-ak": self.access_key_id,
            "x-timestamp": timestamp,
            "x-sign": signature,
            "Content-Type": "application/json"
        }

    def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> requests.Response:
        """Initiate a GET request"""
        headers = self.sign_get_request(path, params)
        url = self.base_url + path
        return requests.get(url, params=params, headers=headers)

    def post(self, path: str, data: Dict[str, Any]) -> requests.Response:
        """Initiate a POST request"""
        headers = self.sign_post_request(path, data)
        url = self.base_url + path
        request_body = json.dumps(data, ensure_ascii=False, separators=(',', ':'))
        return requests.post(url, data=request_body, headers=headers)

# Use examples
if __name__ == "__main__":
    # Initialize the client - select the domain name based on the region
    # Domestic users
    client = ArmCloudSignatureV2("LTAI4FzK8888888888888", "your_secret_key", "https://api.xiaosuanyun.com")
    # Overseas users
    # client = ArmCloudSignatureV2("LTAI4FzK8888888888888", "your_secret_key", "https://openapi-hk.armcloud.net")

    # POST Request Example - Board List Query (For interface paths, please refer to the OpenAPI documentation)
    device_list_data = {
        "page": 1,
        "rows": 10,
        "padCodes": ["AC21020010391"],
        "vmStatus": "1",
        "deviceStatus": "0"
    }

    try:
        # Note: The path shown here is for example only. Please refer to the OpenAPI documentation for the actual path
        response = client.post("/openapi/open/device/list", device_list_data)
        print(f"Board List Query Response: {response.status_code}")
        print(f"Response Body: {response.text}")
    except Exception as e:
        print(f"Board list query Error: {e}")

    # POST Request Example - Creating a Cloud Instance (For interface paths and parameters, please refer to the OpenAPI documentation)
    create_instance_data = {
        "clusterCode": "001",
        "specificationCode": "m2-3",
        "imageId": "img-25021828327",
        "screenLayoutCode": "realdevice_1440x3120x560",
        "number": 2,
        "dns": "8.8.8.8",
        "storageSize": 16,
        "realPhoneTemplateId": 36
    }

    try:
        # Note: The path shown here is for example only. Please refer to the OpenAPI documentation for the actual path
        response = client.post("/openapi/open/pad/net/storage/res/create", create_instance_data)
        print(f"Create Cloud Instance Response: {response.status_code}")
        print(f"Response Body: {response.text}")
    except Exception as e:
        print(f"Create Cloud Instance Error: {e}")

Node.js implementation example

const crypto = require('crypto');
const axios = require('axios');

class ArmCloudSignatureV2 {
    constructor(accessKeyId, secretKey, baseUrl = 'https://api.xiaosuanyun.com') {
        this.accessKeyId = accessKeyId;
        this.secretKey = secretKey;
        this.baseUrl = baseUrl; // Domestic: https://api.xiaosuanyun.com Overseas: https://openapi-hk.armcloud.net
    }

    calculateSignature(timestamp, path, body = '') {
        const stringToSign = timestamp + path + body;
        return crypto
            .createHmac('sha256', this.secretKey)
            .update(stringToSign, 'utf8')
            .digest('hex');
    }

    signGetRequest(path, params = {}) {
        const timestamp = Date.now().toString();
        const queryString = new URLSearchParams(params).toString();
        const body = queryString ? queryString : '';
        const signature = this.calculateSignature(timestamp, path, body);

        return {
            'authver': '2.0',
            'x-ak': this.accessKeyId,
            'x-timestamp': timestamp,
            'x-sign': signature
        };
    }

    signPostRequest(path, data) {
        const timestamp = Date.now().toString();
        const requestBody = JSON.stringify(data);
        const signature = this.calculateSignature(timestamp, path, requestBody);

        return {
            'authver': '2.0',
            'x-ak': this.accessKeyId,
            'x-timestamp': timestamp,
            'x-sign': signature,
            'Content-Type': 'application/json'
        };
    }

    async get(path, params = {}) {
        const headers = this.signGetRequest(path, params);
        return await axios.get(this.baseUrl + path, { headers, params });
    }

    async post(path, data) {
        const headers = this.signPostRequest(path, data);
        return await axios.post(this.baseUrl + path, data, { headers });
    }
}

// Example of use – selecting a domain name based on region
// Domestic users
const client = new ArmCloudSignatureV2('LTAI4FzK8888888888888', 'your_secret_key', 'https://api.xiaosuanyun.com');
// Overseas users
// const client = new ArmCloudSignatureV2('LTAI4FzK8888888888888', 'your_secret_key', 'https://openapi-hk.armcloud.net');

// POST Request - Board List Query (For interface paths, please refer to the OpenAPI documentation)
const deviceListData = {
    page: 1,
    rows: 10,
    padCodes: ['AC21020010391'],
    vmStatus: '1',
    deviceStatus: '0'
};

// Note: The path shown here is for example only. Please refer to the OpenAPI documentation for the actual path
client.post('/openapi/open/device/list', deviceListData)
    .then(response => console.log('Board List Query Response:', response.data))
    .catch(error => console.error('Board list query Error:', error.message));

// POST request - Create cloud instance (interface paths and parameters refer to the OpenAPI documentation)
const createInstanceData = {
    clusterCode: '001',
    specificationCode: 'm2-3',
    imageId: 'img-25021828327',
    screenLayoutCode: 'realdevice_1440x3120x560',
    number: 2,
    dns: '8.8.8.8',
    storageSize: 16,
    realPhoneTemplateId: 36
};

// Note: The path shown here is for example only. Please refer to the OpenAPI documentation for the actual path
client.post('/openapi/open/pad/net/storage/res/create', createInstanceData)
    .then(response => console.log('Creating a Cloud Instance Response:', response.data))
    .catch(error => console.error('Creating Cloud Instance Error:', error.message));
Prev
Product billing
Next
Android SDK