Skip to content

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 FieldRequiredDescription
X-Access-KeyYesAPI access key ID, used to identify the caller
X-TimestampYesMillisecond-level Unix timestamp (13 digits), used for replay attack prevention
X-NonceYesRandom string, 10~32 characters of lowercase letters and digits, unique per request
X-SignatureYesSignature 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 + accessKey

Step 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 ​

ParameterTypeAllowed ValuesDescription
uriPathStringe.g. /api/v1/ordersRequest path, excluding domain and query parameters
timestampString13-digit integerMillisecond-level Unix timestamp
nonceString10~32 charactersRandom combination of lowercase letters a-z and digits 0-9
accessKeyString-API Access Key ID, consistent with X-Access-Key
secretKeyStringConfidentialAPI Secret Access Key, used only for signature calculation, never transmitted

Signature Example ​

Example Parameters:

ParameterExample Value
uriPath/api/v1/orders
timestamp1719800000000
noncea3f7b2c9d1e8
accessKeyak-7x9k2m4p
secretKeysk-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:

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}

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" + accessKey

Step 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 ​

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:

java
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 ​

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
ScenarioCorrectIncorrect
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 concatenationDirectly concatenate with original case

OpenAPI Docs