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:
- Log in to the ARMCloud platform
- Go to the User Management - > Account Information menu
- 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
| Region | Domain name | Scope of application |
|---|---|---|
| Domestic | https://api.xiaosuanyun.com | Users in Chinese mainland |
| Overseas | https://openapi-hk.armcloud.net | Users 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 name | Explanation | Example |
|---|---|---|
| authver | Authentication version, fixed value "2.0" | 2.0 |
| x-ak | Access key IDs are assigned by the ArmCloud platform | LTAI4FzK8888888888888 |
| x-timestamp | Request initiation timestamp (milliseconds) | 1618900299000 |
| x-sign | Request signing results | 5d9f846a5254865894069d9a864a96696115f0edcac66de5f3a3515f58c44200 |
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:
| Parameters | Explanation | Example |
|---|---|---|
timestamp | A 13-bit millisecond timestamp, the same as the x-timestamp request header | 1618900299000 |
path | A complete API path must start with / | /openapi/open/device/list |
body | Different content depending on the type of request (see explanation below) | See specific examples |
Body Content Rules:
GET Requests:
- If query parameters are present:
body = query parameter string - If no query parameters:
body = ""(empty string)
- If query parameters are present:
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 type | QPS limit (per second) | RPM limit (per minute) | Explanation |
|---|---|---|---|
| Test Users | 200 times per second | 5,000 times per minute | Free users are restricted by default |
| Paying Users | 2,000 times per second | 30,000 beats per minute | Restrictions after paid upgrades |
Explanation of Rate Limiting Dimensions:
- QPS Limit: The maximum number of requests within any 1-second time window
- Per Minute Limit: The maximum number of requests within any 1-minute time window
- Access Key Level: Each Access Key is calculated independently; the quota is not shared between different Keys
- 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 head | Explanation | Example |
|---|---|---|
X-RateLimit-Limit | The request limit for the current rate limiting cycle depends on the type of throttling | 200 |
X-RateLimit-Remaining | The number of available requests remaining in the current rate limiting period | 150 |
X-RateLimit-Reset | Current rate limiting cycle reset timestamp (Unix timestamp) | 1618900361 |
X-RateLimit-Type | The type of rate limit triggered | QPS/RPM |
Best Practices
suggestion
- 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
- Implement Retry Mechanism: When receiving a 429 error, wait for an appropriate time before retrying; it is recommended to use the exponential avoidance algorithm
- Monitoring Response Heads: Focus on
X-RateLimit-*response heads and actively control the rhythm of requests - 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
- Please ensure the system time is accurate; the server allows a maximum time deviation of 5 minutes
- SecretKey is sensitive information. Do not store or transmit it in plaintext. It is recommended to use environment variables for
- Each request must be re-signed and cannot be reused
- Signature calculations must include the complete request path, including the front slash
- JSON must be in a compact format and cannot contain extra spaces or line breaks
- All strings must use UTF-8 encoding
- 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));