API Calling Guide
The OpenAPI provides standard HTTP endpoints for managing cloud phone instances, tasks, and related resources.
Quick Start
Obtain API credentials (Access Key ID / Secret Access Key) from the Console -> Developer -> Open API page, and pass the obtained Access Key ID via the X-Access-Key header on all API requests.
API Rules
Request Headers
Every API request must include the following four fields in the HTTP Header:
| Header Field | Required | Description |
|---|---|---|
| X-Access-Key | Yes | API access key ID, used to identify the caller |
| X-Timestamp | Yes | Millisecond-level Unix timestamp (13 digits), used for replay attack prevention |
| X-Nonce | Yes | Random string, 10~32 characters of lowercase letters and digits, unique per request |
| X-Signature | Yes | Signature value, Base64-encoded HMAC-SHA256 result |
WARNING
All four header fields are required. Missing or invalid fields will result in a 401 Unauthorized response.
Callbacks
All callback-supporting APIs will send data to the user-provided callback URL.
The callback URL can be modified in Console -> Developer -> Open API .
Callback messages carry a signature. The receiver should verify the signature using the Callback Signature Algorithm to ensure the message is authentic and untampered.
Signature Algorithm
The signature uses the HMAC-SHA256 algorithm. The calculation process is as follows:
Step 1: Construct the signing string (SignString)
Concatenate the following four parts in fixed order, without any separator:
SignString = uriPath + timestamp + nonce + accessKeyStep 2: Convert to lowercase
SignString = SignString.toLowerCase()Step 3: HMAC-SHA256 calculation
Use the secretKey as the key to perform HMAC-SHA256 on the lowercased signing string, producing a byte array.
Step 4: Base64 encoding
Base64-encode the byte array from the HMAC-SHA256 calculation to get the final signature value.
Signature Formula
X-Signature = Base64( HMAC-SHA256( SignString.toLowerCase(), secretKey ) )Parameter Reference
| Parameter | Type | Allowed Values | Description |
|---|---|---|---|
| uriPath | String | e.g. /api/v1/orders | Request path, excluding domain and query parameters |
| timestamp | String | 13-digit integer | Millisecond-level Unix timestamp |
| nonce | String | 10~32 characters | Random combination of lowercase letters a-z and digits 0-9 |
| accessKey | String | - | API Access Key ID, consistent with X-Access-Key |
| secretKey | String | Confidential | API Secret Access Key, used only for signature calculation, never transmitted |
Signature Example
Example Parameters:
| Parameter | Example Value |
|---|---|
| uriPath | /api/v1/orders |
| timestamp | 1719800000000 |
| nonce | a3f7b2c9d1e8 |
| accessKey | ak-7x9k2m4p |
| secretKey | sk-5n8q3r6t1w9y2u4i7o0p |
Calculation Process:
Step 1: Construct the signing string
SignString = "/api/v1/orders" + "1719800000000" + "a3f7b2c9d1e8" + "ak-7x9k2m4p"
= "/api/v1/orders1719800000000a3f7b2c9d1e8ak-7x9k2m4p"
Step 2: Convert to lowercase
SignString = "/api/v1/orders1719800000000a3f7b2c9d1e8ak-7x9k2m4p".toLowerCase()
Step 3 & 4: HMAC-SHA256 + Base64
signature = Base64( HMAC-SHA256(SignString, secretKey) )Final Request Example:
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}Callback Signature Algorithm
Callback messages also use HMAC-SHA256 signatures. The difference from request signatures is that the four fields of the signing string are separated by a single line feed \n (ASCII 0x0A), and the entire concatenated string must be converted to lowercase.
Step 1: Construct the signing string (SignString)
Concatenate the following four fields in fixed order, separated by a single line feed \n:
SignString = uriPath + "\n" + timestamp + "\n" + nonce + "\n" + accessKeyStep 2: Convert to lowercase
SignString = SignString.toLowerCase()Step 3: HMAC-SHA256 calculation
Using the secretKey agreed by both parties (the Secret Access Key on the web console, never transmitted) as the HMAC key, perform HmacSHA256 on the lowercased signing string to produce a 32-byte binary digest.
Step 4: Base64 encoding
Base64-encode the digest byte array. The resulting string is the signature value.
Signature formula:
sign = Base64( HmacSHA256( SecretKey, toLowerCase(uriPath + "\n" + timestamp + "\n" + nonce + "\n" + accessKey) ) )TIP
Callback signatures use the same parameters and key as request signatures, but with a different concatenation: request signatures concatenate the four parts directly with no separator; callback signatures separate the four fields with a single line feed \n.
Code Examples
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";
/**
* Generate signed request headers
*/
public static SignatureResult sign(String uriPath, String accessKey, String secretKey) {
// 1. Generate 13-digit millisecond timestamp
String timestamp = String.valueOf(System.currentTimeMillis());
// 2. Generate 10~32 character random nonce
String nonce = generateNonce(10, 32);
// 3. Construct signing string and convert to lowercase
String signString = uriPath + timestamp + nonce + accessKey;
signString = signString.toLowerCase();
// 4. HMAC-SHA256 + Base64 encode
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("Signature calculation failed", 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;
}
}
}Usage:
String uriPath = "/api/v1/orders";
String accessKey = "ak-7x9k2m4p";
String secretKey = "sk-5n8q3r6t1w9y2u4i7o0p";
ApiSignatureUtil.SignatureResult result =
ApiSignatureUtil.sign(uriPath, accessKey, secretKey);
// Set request headers
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
import hmac
import hashlib
import base64
import time
import random
import string
def generate_nonce(min_len=10, max_len=32):
"""Generate a random 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):
"""Generate signature and request header parameters"""
timestamp = str(int(time.time() * 1000))
nonce = generate_nonce()
# Construct signing string and convert to lowercase
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
}Best Practices
Security
DANGER
The secretKey is the core key for signature calculation. Treat it as a password and never:
- Hardcode the secretKey in frontend code (browser/App)
- Commit the secretKey to public code repositories (e.g., GitHub, GitLab public projects)
- Print the secretKey in logs or error messages
- Transmit the secretKey in plaintext over HTTP
It is recommended to store the secretKey using environment variables or a key management system (e.g., Vault, KMS) to avoid plaintext appearance in code.
Nonce Uniqueness
X-Nonce must be unique within its validity period (typically aligned with the timestamp window, recommended 5 minutes). The server maintains a list of used nonces for deduplication. Reused nonces will be rejected.
Time Synchronization
The server's allowed time deviation window is recommended to be set to ±5 minutes. Ensure the caller's server maintains NTP time synchronization to avoid signature failures due to time drift.
URI Path Conventions
When constructing the signature, uriPath must follow these rules:
- Only include the path portion, excluding domain, port, and query parameters
- Keep the original case, then convert the entire concatenated string to lowercase
- Start with
/, do not end with/(unless the API defines it that way) - Do not include URL encoding; use the original path characters
| Scenario | Correct | Incorrect |
|---|---|---|
| Full URL | /api/v1/users | /api/v1/users |
| With query params | /api/v1/search | /api/v1/search?q=test |
| Mixed-case path | /API/V1/Users → lowercase after concatenation | Directly concatenate with original case |
